Identity Server EntityFramework Core
到目前為止我們已經成功架設了 Identity Server 服務但是目前資料都是保存在記憶體內,今天來看看該怎麼把資料利用 EFCore 保存到任意資料庫內
首先先回顧一下當初 Identity Server 使用記憶體儲存的設定
builder.Services.AddIdentityServer(options =>
{
// https://docs.duendesoftware.com/identityserver/v6/fundamentals/resources/api_scopes#authorization-based-on-scopes
options.EmitStaticAudienceClaim = true;
})
.AddInMemoryIdentityResources(Config.IdentityResources)
.AddInMemoryApiScopes(Config.ApiScopes)
.AddInMemoryClients(Config.Clients)
.AddTestUsers(TestUsers.Users);
在這裡我們呼叫了三個 AddInMemory 方法將對應的資料添加到記憶體內,接下來的目標就是把這些方法替換掉
這邊在 IdentityServer 添加 Duende.IdentityServer.EntityFramework
和各自的 EFCore Package 這邊選用的是 PostgreSQL
dotnet add package Duende.IdentityServer.EntityFramework
dotnet add package Npgsql.EntityFrameworkCore.PostgreSQL
dotnet add package Microsoft.EntityFrameworkCore.Tools
使用 EF 第一步需要先建立一個 DbContext,這邊我們可以使用 Identity Server 預設提供的 DbContext
並且依照功能性區分成兩個 DbContext 分別為 ConfigurationDbContext
與 PersistedGrantDbContext
ConfigurationDbContext
包含以下 DBSet
- Client
- ClientCorsOrigin
- IdentityResource
- ApiResource
- ApiScope
- IdentityProvider
PersistedGrantDbContext
包含以下 DBSet
- PersistedGrant
- DeviceFlowCodes
- Key
- ServerSideSession
接下來將 AddInMemory 替換成 AddConfigurationStore
與 AddOperationalStore
方法
var migrationsAssembly = typeof(Program).Assembly.GetName().Name;
const string connectionStringConfigure = @"User ID=postgres;Password=myPassw0rd;Host=localhost;Port=5432;Database=IdentityServerConfigure;Pooling=false;";
const string connectionStringOperational = @"User ID=postgres;Password=myPassw0rd;Host=localhost;Port=5432;Database=IdentityServerOperational;Pooling=false;";
builder.Services.AddIdentityServer(options =>
{
// https://docs.duendesoftware.com/identityserver/v6/fundamentals/resources/api_scopes#authorization-based-on-scopes
options.EmitStaticAudienceClaim = true;
})
.AddConfigurationStore(options =>
{
options.ConfigureDbContext = b => b.UseNpgsql(connectionStringConfigure,
sql => sql.MigrationsAssembly(migrationsAssembly));
})
.AddOperationalStore(options =>
{
options.ConfigureDbContext = b => b.UseNpgsql(connectionStringOperational,
sql => sql.MigrationsAssembly(migrationsAssembly));
})
.AddClientStore<ClientStore>()
.AddTestUsers(TestUsers.Users);
我們可以稍微看一下 AddConfigurationStore
方法的內容
public static IIdentityServerBuilder AddConfigurationStore<TContext>(
this IIdentityServerBuilder builder,
Action<ConfigurationStoreOptions> storeOptionsAction = null)
where TContext : DbContext, IConfigurationDbContext
{
builder.Services.AddConfigurationDbContext<TContext>(storeOptionsAction);
builder.AddClientStore<ClientStore>();
builder.AddResourceStore<ResourceStore>();
builder.AddCorsPolicyService<CorsPolicyService>();
builder.AddIdentityProviderStore<IdentityProviderStore>();
return builder;
}
從內容可以得知這個方法只是用來註冊 DbContext 與註冊多個 Store 方法,所以基本上這個方法不是必要的 如果你想要用自己寫的 DbContext 可以直接跳過這個方法直接呼叫下方那四個 AddStore 方法即可
接下來分別幫這兩個 DbContext 建立遷移腳本與更新資料庫
dotnet ef migrations add InitialIdentityServerConfigurationDbMigration -c ConfigurationDbContext -o Data/Migrations/IdentityServer/ConfigurationDb
dotnet ef migrations add InitialIdentityServerPersistedGrantDbMigration -c PersistedGrantDbContext -o Data/Migrations/IdentityServer/PersistedGrantDb
dotnet ef database update -c ConfigurationDbContext
dotnet ef database update -c PersistedGrantDbContext
到此兩個資料庫就建立出來了,可以額外建立一個方法將原本使用到資料寫到資料庫內
private static void InitializeDatabase(IApplicationBuilder app)
{
using (var serviceScope = app.ApplicationServices.GetService<IServiceScopeFactory>().CreateScope())
{
serviceScope.ServiceProvider.GetRequiredService<PersistedGrantDbContext>().Database.Migrate();
var context = serviceScope.ServiceProvider.GetRequiredService<ConfigurationDbContext>();
context.Database.Migrate();
if (!context.Clients.Any())
{
foreach (var client in Config.Clients)
{
context.Clients.Add(client.ToEntity());
}
context.SaveChanges();
}
if (!context.IdentityResources.Any())
{
foreach (var resource in Config.IdentityResources)
{
context.IdentityResources.Add(resource.ToEntity());
}
context.SaveChanges();
}
if (!context.ApiScopes.Any())
{
foreach (var resource in Config.ApiScopes)
{
context.ApiScopes.Add(resource.ToEntity());
}
context.SaveChanges();
}
}
}
ConfigurePipeline 內部呼叫此方法
InitializeDatabase(app);
完成後執行 Identity Server 專案與 WebClient 專案可以正常登入就代表成功了
Summary
今天成功將保存在記憶體的資料改成保存到資料庫內,之後只要建立相關的頁面就可以將資料永久保存了 不過像是建立 Client 或 ApiScope 這些頁面 Identity Server 預設並沒有提供必須要搭配其他開源的 admin 專案 才可以在頁面上管理這些資源,有需要的可以到 Github 尋找開源的專案來使用