自訂.NET Core Identity身份驗證和授權教學:Identity Server EFCore 自訂.NET Core Identity身份驗證和授權教學:Identity Server EFCore

Published on Monday, May 8, 2023

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 分別為 ConfigurationDbContextPersistedGrantDbContext

ConfigurationDbContext 包含以下 DBSet

  • Client
  • ClientCorsOrigin
  • IdentityResource
  • ApiResource
  • ApiScope
  • IdentityProvider

PersistedGrantDbContext 包含以下 DBSet

  • PersistedGrant
  • DeviceFlowCodes
  • Key
  • ServerSideSession

接下來將 AddInMemory 替換成 AddConfigurationStoreAddOperationalStore 方法

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 尋找開源的專案來使用