feat: add data protection key management and update authentication settings
- Added support for data protection keys by integrating Entity Framework Core for key persistence. - Updated `Program.cs` and `MemberCenterDbContext.cs` to configure data protection services. - Introduced new migration to create `DataProtectionKeys` table in the database. - Enhanced cookie settings for authentication to enforce security policies (SameSite=None, Secure=Always). - Updated installation documentation with new authentication configuration options.
This commit is contained in:
parent
e77fdec76b
commit
6729f91275
@ -41,6 +41,9 @@
|
|||||||
ASPNETCORE_ENVIRONMENT=Development
|
ASPNETCORE_ENVIRONMENT=Development
|
||||||
ConnectionStrings__Default=Host=localhost;Database=member_center;Username=postgres;Password=postgres
|
ConnectionStrings__Default=Host=localhost;Database=member_center;Username=postgres;Password=postgres
|
||||||
Auth__Issuer=http://localhost:7850/
|
Auth__Issuer=http://localhost:7850/
|
||||||
|
Auth__WebLoginUrl=http://localhost:5080/account/login
|
||||||
|
Auth__AllowedLoginReturnUrlPrefixes=http://localhost:7850/
|
||||||
|
Auth__AllowedLogoutReturnUrlPrefixes=http://localhost:5243/
|
||||||
Auth__Resources__MemberCenter__Audience=member_center_api
|
Auth__Resources__MemberCenter__Audience=member_center_api
|
||||||
Auth__Resources__SendEngine__Audience=send_engine_api
|
Auth__Resources__SendEngine__Audience=send_engine_api
|
||||||
Auth__Resources__FileAccess__Audience=file_access_api
|
Auth__Resources__FileAccess__Audience=file_access_api
|
||||||
@ -59,6 +62,12 @@ SendEngine__WebhookSecret=change-me
|
|||||||
- 規劃上將收斂為 DB resource registry;`.env` 僅作為初始 seed / 部署覆寫來源,不應再為每個新服務新增平行 hardcoded key。
|
- 規劃上將收斂為 DB resource registry;`.env` 僅作為初始 seed / 部署覆寫來源,不應再為每個新服務新增平行 hardcoded key。
|
||||||
- `File Access` 已直接採用 resource registry 形式,不新增第三組硬編碼 audience 判斷。
|
- `File Access` 已直接採用 resource registry 形式,不新增第三組硬編碼 audience 判斷。
|
||||||
|
|
||||||
|
OIDC / Redirect login 設定說明:
|
||||||
|
- `Auth__WebLoginUrl`: API `/oauth/authorize` 未登入時導向的 Web login URL。
|
||||||
|
- `Auth__AllowedLoginReturnUrlPrefixes`: Web login 成功後允許 redirect 回去的 URL prefix,通常填 API issuer/base URL。
|
||||||
|
- `Auth__AllowedLogoutReturnUrlPrefixes`: Web logout 後允許 redirect 的 URL prefix。
|
||||||
|
- Identity cookie 固定使用 `SameSite=None`、`Secure=Always`、`Path=/`,因此 stage/prod 必須使用 HTTPS。
|
||||||
|
|
||||||
`SendEngine` 設定說明:
|
`SendEngine` 設定說明:
|
||||||
- `SendEngine__BaseUrl`: Send Engine API base URL
|
- `SendEngine__BaseUrl`: Send Engine API base URL
|
||||||
- `SendEngine__WebhookSecret`: 與 Send Engine `Webhook:Secrets:member_center` 一致
|
- `SendEngine__WebhookSecret`: 與 Send Engine `Webhook:Secrets:member_center` 一致
|
||||||
|
|||||||
@ -25,7 +25,8 @@ var issuerUri = ParseAbsoluteUriOrThrow(issuer, "Auth:Issuer");
|
|||||||
var allowInsecureHttp = builder.Configuration.GetValue("Auth:AllowInsecureHttp", false);
|
var allowInsecureHttp = builder.Configuration.GetValue("Auth:AllowInsecureHttp", false);
|
||||||
|
|
||||||
builder.Services.AddDataProtection()
|
builder.Services.AddDataProtection()
|
||||||
.SetApplicationName("MemberCenter");
|
.SetApplicationName("MemberCenter")
|
||||||
|
.PersistKeysToDbContext<MemberCenterDbContext>();
|
||||||
|
|
||||||
builder.Services.AddDbContext<MemberCenterDbContext>(options =>
|
builder.Services.AddDbContext<MemberCenterDbContext>(options =>
|
||||||
{
|
{
|
||||||
@ -61,11 +62,9 @@ builder.Services.AddAuthentication(options =>
|
|||||||
|
|
||||||
builder.Services.ConfigureApplicationCookie(options =>
|
builder.Services.ConfigureApplicationCookie(options =>
|
||||||
{
|
{
|
||||||
var cookieDomain = builder.Configuration["Auth:CookieDomain"];
|
options.Cookie.Path = "/";
|
||||||
if (!string.IsNullOrWhiteSpace(cookieDomain))
|
options.Cookie.SameSite = SameSiteMode.None;
|
||||||
{
|
options.Cookie.SecurePolicy = CookieSecurePolicy.Always;
|
||||||
options.Cookie.Domain = cookieDomain;
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
builder.Services.AddOpenIddict()
|
builder.Services.AddOpenIddict()
|
||||||
|
|||||||
@ -6,6 +6,7 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Microsoft.AspNetCore.DataProtection.EntityFrameworkCore" Version="8.0.11" />
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="8.0.11" />
|
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="8.0.11" />
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="8.0.11" />
|
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="8.0.11" />
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="8.0.11">
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="8.0.11">
|
||||||
|
|||||||
@ -1,12 +1,13 @@
|
|||||||
using MemberCenter.Domain.Entities;
|
using MemberCenter.Domain.Entities;
|
||||||
using MemberCenter.Infrastructure.Identity;
|
using MemberCenter.Infrastructure.Identity;
|
||||||
|
using Microsoft.AspNetCore.DataProtection.EntityFrameworkCore;
|
||||||
using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
|
using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
|
||||||
namespace MemberCenter.Infrastructure.Persistence;
|
namespace MemberCenter.Infrastructure.Persistence;
|
||||||
|
|
||||||
public class MemberCenterDbContext
|
public class MemberCenterDbContext
|
||||||
: IdentityDbContext<ApplicationUser, ApplicationRole, Guid>
|
: IdentityDbContext<ApplicationUser, ApplicationRole, Guid>, IDataProtectionKeyContext
|
||||||
{
|
{
|
||||||
public MemberCenterDbContext(DbContextOptions<MemberCenterDbContext> options)
|
public MemberCenterDbContext(DbContextOptions<MemberCenterDbContext> options)
|
||||||
: base(options)
|
: base(options)
|
||||||
@ -27,6 +28,7 @@ public class MemberCenterDbContext
|
|||||||
public DbSet<AuthResourceScope> AuthResourceScopes => Set<AuthResourceScope>();
|
public DbSet<AuthResourceScope> AuthResourceScopes => Set<AuthResourceScope>();
|
||||||
public DbSet<AuthClientUsagePermission> AuthClientUsagePermissions => Set<AuthClientUsagePermission>();
|
public DbSet<AuthClientUsagePermission> AuthClientUsagePermissions => Set<AuthClientUsagePermission>();
|
||||||
public DbSet<FileAccessDownloadToken> FileAccessDownloadTokens => Set<FileAccessDownloadToken>();
|
public DbSet<FileAccessDownloadToken> FileAccessDownloadTokens => Set<FileAccessDownloadToken>();
|
||||||
|
public DbSet<DataProtectionKey> DataProtectionKeys { get; set; } = null!;
|
||||||
|
|
||||||
protected override void OnModelCreating(ModelBuilder builder)
|
protected override void OnModelCreating(ModelBuilder builder)
|
||||||
{
|
{
|
||||||
|
|||||||
1321
src/MemberCenter.Infrastructure/Persistence/Migrations/20260430200729_AddDataProtectionKeys.Designer.cs
generated
Normal file
1321
src/MemberCenter.Infrastructure/Persistence/Migrations/20260430200729_AddDataProtectionKeys.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,36 @@
|
|||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace MemberCenter.Infrastructure.Persistence.Migrations
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public partial class AddDataProtectionKeys : Migration
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "DataProtectionKeys",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
Id = table.Column<int>(type: "integer", nullable: false)
|
||||||
|
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
|
||||||
|
FriendlyName = table.Column<string>(type: "text", nullable: true),
|
||||||
|
Xml = table.Column<string>(type: "text", nullable: true)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("PK_DataProtectionKeys", x => x.Id);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "DataProtectionKeys");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -783,6 +783,25 @@ namespace MemberCenter.Infrastructure.Persistence.Migrations
|
|||||||
b.ToTable("users", (string)null);
|
b.ToTable("users", (string)null);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Microsoft.AspNetCore.DataProtection.EntityFrameworkCore.DataProtectionKey", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
|
||||||
|
|
||||||
|
b.Property<string>("FriendlyName")
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<string>("Xml")
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.ToTable("DataProtectionKeys");
|
||||||
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<System.Guid>", b =>
|
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<System.Guid>", b =>
|
||||||
{
|
{
|
||||||
b.Property<int>("Id")
|
b.Property<int>("Id")
|
||||||
|
|||||||
@ -19,7 +19,8 @@ EnvLoader.LoadDotEnvIfDevelopment();
|
|||||||
var builder = WebApplication.CreateBuilder(args);
|
var builder = WebApplication.CreateBuilder(args);
|
||||||
|
|
||||||
builder.Services.AddDataProtection()
|
builder.Services.AddDataProtection()
|
||||||
.SetApplicationName("MemberCenter");
|
.SetApplicationName("MemberCenter")
|
||||||
|
.PersistKeysToDbContext<MemberCenterDbContext>();
|
||||||
|
|
||||||
builder.Services.AddDbContext<MemberCenterDbContext>(options =>
|
builder.Services.AddDbContext<MemberCenterDbContext>(options =>
|
||||||
{
|
{
|
||||||
@ -70,11 +71,9 @@ if (!string.IsNullOrWhiteSpace(googleClientId) && !string.IsNullOrWhiteSpace(goo
|
|||||||
builder.Services.ConfigureApplicationCookie(options =>
|
builder.Services.ConfigureApplicationCookie(options =>
|
||||||
{
|
{
|
||||||
options.LoginPath = "/account/login";
|
options.LoginPath = "/account/login";
|
||||||
var cookieDomain = builder.Configuration["Auth:CookieDomain"];
|
options.Cookie.Path = "/";
|
||||||
if (!string.IsNullOrWhiteSpace(cookieDomain))
|
options.Cookie.SameSite = SameSiteMode.None;
|
||||||
{
|
options.Cookie.SecurePolicy = CookieSecurePolicy.Always;
|
||||||
options.Cookie.Domain = cookieDomain;
|
|
||||||
}
|
|
||||||
|
|
||||||
options.Events = new CookieAuthenticationEvents
|
options.Events = new CookieAuthenticationEvents
|
||||||
{
|
{
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user