개발이야기/AspNet&C#

IdentityServer 학습 #8 - EntityframeworkCore

Roslyn 2024. 3. 7. 17:21
반응형

그 동안에는 AddInMemoryClients 등을 이용해서 코드에서 직접 정의되었던 것을 DB를 이용한 형태로 변경합니다.

본문의 원문은 다음 링크를 참고합니다.

 

원문링크 : https://docs.duendesoftware.com/identityserver/v7/quickstarts/4_ef/

 

Duende Software Documentation

The most flexible and standards-compliant OpenID Connect and OAuth 2.0 framework for ASP.NET Core.

docs.duendesoftware.com

 

더불어 원문에서는 Database로 Sqlite를 사용했으나, 본문에서는 Sql Server로 변경하였습니다.

 

(1) 먼저 기존에 설치된 IdentityServer 프로젝트에서 Duende.IdentityServer를 삭제해 줍니다.

dotnet remove package Duende.IdentityServer

 

(2) 그리고 Duende.IdentityServer.EntityFramework를 새로 설치해 줍니다.

dotnet add package Duende.IdentityServer.EntityFramework

 

굳이 삭제하고 다시 설치하는 이유는 IdentityServer와 EntityFramework 사이에 버전이 다를 경우, 버전 불일치로 인한 오류가 발생할 수 있기때문에, Entityframework와 버전을 맞추기 위해서 입니다.

 

(3) EntityFramework의 Database 를 추가로 설치해 줍니다.

dotnet add package Microsoft.EntityFrameworkCore.SqlServer

 

(4) IdentityServer의 HostingExtensions.cs 파일에 ConfigureServices 메소드를 다음과 같이 변경해 줍니다.

    public static WebApplication ConfigureServices(this WebApplicationBuilder builder)
    {
        builder.Services.AddRazorPages();

        var migrationsAssembly = typeof(Program).Assembly.GetName().Name;
        SqlConnectionStringBuilder sql = new SqlConnectionStringBuilder();
        sql.DataSource = "localhost";
        sql.UserID = "{database connection id}";
        sql.Password = "{database connection password}";
        sql.InitialCatalog = "{database name}";
        sql.TrustServerCertificate = true;
        string connectionString = sql.ConnectionString;

        builder.Services.AddIdentityServer()
            .AddConfigurationStore(options =>
            {
                options.ConfigureDbContext = b => b.UseSqlServer(connectionString,
                    sql => sql.MigrationsAssembly(migrationsAssembly));
            })
            .AddOperationalStore(options =>
            {
                options.ConfigureDbContext = b => b.UseSqlServer(connectionString,
                    sql => sql.MigrationsAssembly(migrationsAssembly));
            })
            .AddTestUsers(TestUsers.Users);

        return builder.Build();
    }

 

(5) entityframework를 이용해서 database를 관리하기 위해서는 ef 툴을 설치해야 합니다.  해당 툴을 global로 설치해 줍니다.

dotnet tool install --global dotnet-ef

 

(6) 다음으로 IdentityServer에 design 툴을 추가해 줍니다.

dotnet add package Microsoft.EntityFrameworkCore.Design

 

(7) 이제 ef로 마이그레이션을 생성하면 되는데, 마이그레이션 생성 과정에서 내부적으로 IdentityServer가 수행됩니다.  이때 오류가 발생함으로, 해당 오류를 무시하기 위해 IdentityServer의 Program.cs 파일에 catch 문을 다음과 같이 변경해 줍니다.

catch (Exception ex) when (ex.GetType().Name is not "StopTheHostException")
{
    Log.Fatal(ex, "Unhandled exception");
}

 

(8) 다음 2개의 마이그레이션 코드를 생성해 줍니다.

dotnet ef migrations add InitialIdentityServerPersistedGrantDbMigration -c PersistedGrantDbContext -o Data/Migrations/IdentityServer/PersistedGrantDb
dotnet ef migrations add InitialIdentityServerConfigurationDbMigration -c ConfigurationDbContext -o Data/Migrations/IdentityServer/ConfigurationDb

이때 다음과 같이 오류가 발생하지만, 무시합니다.

 

(9) 데이터베이스를 코드 수준에서 update 해주기 위해 InitializeDatabase 메소드를 작성해 줍니다.

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();
        }
    }
}

 

(10) ConfigurePipeline 함수를 수정합니다.

    public static WebApplication ConfigurePipeline(this WebApplication app)
    { 
        app.UseSerilogRequestLogging();
    
        if (app.Environment.IsDevelopment())
        {
            app.UseDeveloperExceptionPage();
        }

        InitializeDatabase(app);

 

위에서 생성한 InitializeDatabase 메소드를 ConfigurePipeline 함수내에서 호출하여 dotnet ef database update 와 같은 처리를 수행하게 됩니다.

 

이제 프로젝트를 실행하고 Database를 확인해 보면 다음과 같이 Table 들이 생성된 모습을 확인할 수 있습니다.

 

 

반응형