IdentityServer4系列 | 支持数据持久化

1、前言

在前面的篇章介绍中,一些基础配置如API资源、客户端资源等数据以及使用过程当中发放的令牌等操做数据,咱们都是经过将操做数据和配置数据存储在内存中进行实现的,而在实际开发生产中,咱们须要考虑如何处理数据持久化呢?html

这时IdentityServer4具备良好的扩展性,其中一个可扩展点是用于IdentityServer所需数据的存储机制,进行持久化操做。git

下面将如何配置IdentityServer以使用EntityFramework(EF)做为此数据的存储机制把这些数据存储到Sql Server数据库, 这样更符合咱们实际生产环境的需求。github

2、初识

在咱们的 IdentityServer4中官方定义的两个上下文,是有两种类型的数据须要持久化到数据库中:sql

一、配置数据(资源、客户端、身份);//这里是对应配置上下文 ConfigurationDbContextshell

二、IdentityServer在使用时产生的 操做数据(令牌,代码和用户的受权信息consents);//这里是对应操做上下文 PersistedGrantDbContext数据库

这两个上下文以及对应的数据模型,已经被 IdentityServer4 官方给封装好了, 咱们不须要作额外的操做,直接进行迁移便可使用。json

2.1 ConfigurationDb

ConfigurationDbContext (IdentityServer configuration data) —— 负责数据库中对客户端、资源和 CORS 设置的配置存储;网络

若是须要从 EF 支持的数据库加载客户端、标识资源、API 资源或 CORS 数据 (而不是使用内存中配置), 则可使用配置存储。此支持提供 IClientStoreIResura StoreICorsPolicyService 扩展性点的实现。这些实现使用名为 ConfigurationDbContextdbcontext 派生类对数据库中的表进行建模。antd

2.2 PersistedGrantDb

PersistedGrantDbContext (IdentityServer operational data.) -—— 负责存储赞成、受权代码、刷新令牌和引用令牌;app

若是须要从 EF 支持的数据库 (而不是默认的内存数据库) 加载受权授予、赞成和令牌 (刷新和引用), 则可使用操做存储。此支持提供了 IPersistedGrantStore 扩展点的实现。实现使用名为 PersistedGrantDbContextdbcontext 派生类对数据库中的表进行建模。

3、实践

3.1 新建站点

创建一个MVC的Asp.Net Core项目 ,使用MVC模板

3.2 Nuget包

IdentityServer4.EntityFramework以及EF相关包

1.IdentityServer4
2.IdentityServer4.AspNetIdentity
3.IdentityServer4.EntityFramework

由于本文中使用的是SqlServer数据库,因此须要安装对应的EF程序包对数据库的支持。

Microsoft.EntityFrameworkCore.SqlServer

3.3 数据库上下文

appsettings.json

"ConnectionStrings": {
    "DataContext": "data source=.;initial catalog=Yuan.Idp;user id=sa;password=123456;",   
  }

配置链接数据库

var connectionString = Configuration.GetConnectionString("DataContext");
 if (connectionString == "")
 {
      throw new Exception("数据库配置异常");
 }

2.配置数据库服务

在startup.cs中ConfigureServices方法添加以下代码:

public void ConfigureServices(IServiceCollection services)
        {
            services.AddControllersWithViews();
            var connectionString = Configuration.GetConnectionString("DataContext");
            if (connectionString == "")
            {
                throw new Exception("数据库配置异常");
            }
            var migrationsAssembly = typeof(Startup).GetTypeInfo().Assembly.GetName().Name;
            // in DB  config
            var builder = services.AddIdentityServer(options =>
            {
                options.Events.RaiseErrorEvents = true;
                options.Events.RaiseInformationEvents = true;
                options.Events.RaiseFailureEvents = true;
                options.Events.RaiseSuccessEvents = true;
            }).AddConfigurationStore(options => //添加配置数据(ConfigurationDbContext上下文用户配置数据)
            {
                options.ConfigureDbContext = builder => builder.UseSqlServer(connectionString, sql => sql.MigrationsAssembly(migrationsAssembly));
            }).AddOperationalStore(options =>   //添加操做数据(PersistedGrantDbContext上下文 临时数据(如受权和刷新令牌))
            {
                options.ConfigureDbContext = builder => builder.UseSqlServer(connectionString, sql => sql.MigrationsAssembly(migrationsAssembly));
                // 自动清理 token ,可选
                options.EnableTokenCleanup = true;
                // 自动清理 token ,可选
                options.TokenCleanupInterval = 30;
            }).AddTestUsers(TestUsers.Users);
            // not recommended for production - you need to store your key material somewhere secure
            builder.AddDeveloperSigningCredential();

            services.ConfigureNonBreakingSameSiteCookies();
        }

3.4 迁移数据

3.4.1 控制台迁移

方法一:

须要添加EF工具,安装Microsoft.EntityFrameworkCore.Tools, 进行迁移

一、add-migration InitialIdentityServerPersistedGrantDbMigration -c PersistedGrantDbContext -o Data/Migrations/PersistedGrantDb 
   二、add-migration InitialIdentityServerConfigurationDbMigration -c ConfigurationDbContext -o Data/Migrations/ConfigurationDb 
   三、update-database -Context PersistedGrantDbContext
   四、update-database -Context ConfigurationDbContext

3.4.2 在命令窗口

方法二:

判断是否支持命令行迁移,你能够在项目所在的目录下打开一个命令 Power shell 并运行命令 dotnet ef, 它应该是这样的:

dotnet ef 没法执行,由于找不到指定的命令或文件

从 3.0 起,EF Core 命令列工具 (dotnet ef) 不在 .NET Core SDK 里面,需另装。命令以下:

dotnet tool install --global dotnet-ef

要建立迁移,请在IdentityServer项目目录中打开命令提示符。 在命令提示符下运行这两个命令:

1. dotnet ef migrations add InitialIdentityServerPersistedGrantDbMigration -c PersistedGrantDbContext -o Data/Migrations/PersistedGrantDb
2. dotnet ef migrations add InitialIdentityServerConfigurationDbMigration -c ConfigurationDbContext -o Data/Migrations/ConfigurationDb
 
#生成
1. update-database -c PersistedGrantDbContext   
2. update-database -c ConfigurationDbContext

3.5 显示数据库

(图片来自网络)

3.6 初始化数据库

在以前的篇章中,咱们是定义的内存配置数据实现的操做,而在本篇中,咱们进行数据持久化操做,能够将以前内存的数据做为种子处理迁移到建立的数据库中进行初始化操做。

参考文章: 用户数据迁移

3.6.1 建立文件

建立SeedData.cs文件,用于初始化基础数据:

public class SeedData
    {
        public static void EnsureSeedData(IServiceProvider serviceProvider)
        {
            Console.WriteLine("Seeding database...");

            using (var scope = serviceProvider.GetRequiredService<IServiceScopeFactory>().CreateScope())
            {
                scope.ServiceProvider.GetService<PersistedGrantDbContext>().Database.Migrate();

               var context = scope.ServiceProvider.GetRequiredService<ConfigurationDbContext>();
                context.Database.Migrate();
                EnsureSeedData(context);
            }

            Console.WriteLine("Done seeding database.");
            Console.WriteLine();
        }
        private static void EnsureSeedData(ConfigurationDbContext context)
        {
            if (!context.Clients.Any())
            {
                Console.WriteLine("Clients 正在初始化");
                foreach (var client in Config.GetClients)
                {
                    context.Clients.Add(client.ToEntity());
                }
                context.SaveChanges();
            }

            if (!context.IdentityResources.Any())
            {
                Console.WriteLine("IdentityResources 正在初始化");
                foreach (var resource in Config.GetIdentityResources)
                {
                    context.IdentityResources.Add(resource.ToEntity());
                }
                context.SaveChanges();
            }

            if (!context.ApiResources.Any())
            {
                Console.WriteLine("ApiResources 正在初始化");
                foreach (var resource in Config.GetApiResources)
                {
                    context.ApiResources.Add(resource.ToEntity());
                }
                context.SaveChanges();
            }

            if (!context.ApiScopes.Any())
            {
                Console.WriteLine("ApiScopes 正在初始化");
                foreach (var resource in Config.GetApiScopes)
                {
                    context.ApiScopes.Add(resource.ToEntity());
                }
                context.SaveChanges();
            }
        }
    }

配置内容能够查看以前篇章内容文件Config.cs 或者项目地址.

3.6.2 调用方法

而后咱们能够从主入口Main方法调用它:

public static void Main(string[] args)
        {
            var seed = args.Contains("/seed");
            if (seed)
            {
                args = args.Except(new[] { "/seed" }).ToArray();
            }
            var host = CreateHostBuilder(args).Build();
            if (seed)
            {
                SeedData.EnsureSeedData(host.Services);
            }
            host.Run();
        }

3.6.3 程序运行

输入 dotnet run /seed 

3.6.4 效果

4、问题

4.1 提示找不到上下文

上面咱们说到了的两个上下文,若是咱们直接经过执行迁移命令是会报错的,好比咱们直接迁移 PersistedGrantDbContext 上下文:

由于迁移的目标不匹配,须要更改迁移程序集,如

options.UseSqlServer(connection, b => b.MigrationsAssembly("Ids4.EFCore"))

因此,就须要在项目中配置对应的服务,咱们在 startup.cs 启动文件中,配置服务 ConfigureService ,配置 EF 操做数据库.

解决方法 : 可参考上面的实践部分中的数据库上下文.

  1. 获取数据库链接字符串

  2. 配置数据库服务

4.2 dotnet ef 没法执行

由于找不到指定的命令或文件

从 3.0 起,EF Core 命令列工具 (dotnet ef) 不在 .NET Core SDK 里面,需另装。命令以下:

dotnet tool install --global dotnet-ef

5、总结

  1. 简单介绍了IdentityServer4持久化存储机制相关配置和操做数据,实现了数据迁移,及应用程序的实践。
  2. 本篇未对用户进行持久化操做存储说明,由于IdentityServer4本就支持了接入其余认证方式,因此本身根据须要进行合理扩展的,好比咱们可使用 Asp.Net Core 自带的 Identity 身份认证机制来实现扩展,固然,你也能够本身定义相应的操做,在后续篇章中会进行说明介绍。
  3. 若是有不对的或不理解的地方,但愿你们能够多多指正,提出问题,一块儿讨论,不断学习,共同进步。
  4. 项目地址

6、附加

EF支持持久化配置和操做数据