IdentityServer4(二)持久化

目录

  • 一、简介
  • 二、客户端(Clients)
  • 三、接口资源(ApiResources)
  • 四、接口作用域(ApiScopes)
  • 五、身份资源(IdentityResources)
  • 六、DeviceCodes
  • 七、PersistedGrants
  • 八、aspnet core使用IdentityServer4

一、简介

  • 这里我对IdentityServer持久化配置中的数据库设计方案进行解释。
  • 数据库使用的是mssql

二、客户端(Clients)

客户端数据模型图

2.1 Clients客户端主表

序号 列名 数据类型 长度 小数位数 主键 自增 允许空 默认值 列说明
1 Id int 主键Id
2 Enabled bit 1 是否有效
3 ClientId nvarchar 200 客户端Id
4 ProtocolType nvarchar 200 协议类型(oidc、wsfed、saml2p)
5 RequireClientSecret bit 1 是否开启秘钥校验
6 ClientName nvarchar 200 客户端名称
7 Description nvarchar 1000 客户端说明
8 ClientUri nvarchar 2000
9 LogoUri nvarchar 2000
10 RequireConsent bit 1
11 AllowRememberConsent bit 1
12 AlwaysIncludeUserClaimsInIdToken bit 1 始终允许token中包含用户声明信息
13 RequirePkce bit 1
14 AllowPlainTextPkce bit 1
15 RequireRequestObject bit 1
16 AllowAccessTokensViaBrowser bit 1 是否允许浏览器传递token
17 FrontChannelLogoutUri nvarchar 2000
18 FrontChannelLogoutSessionRequired bit 1
19 BackChannelLogoutUri nvarchar 2000
20 BackChannelLogoutSessionRequired bit 1
21 AllowOfflineAccess bit 1 是否允许离线工作,获取刷新令牌需要为true
22 IdentityTokenLifetime int 身份令牌生命周期(单位:秒)
23 AllowedIdentityTokenSigningAlgorithms nvarchar 100
24 AccessTokenLifetime int 访问令牌生命周期(单位:秒)
25 AuthorizationCodeLifetime int 授权代码生命周期(单位:秒)
26 ConsentLifetime int 用户同意的生存期(单位:秒)。默认为null(无过期)
27 AbsoluteRefreshTokenLifetime int 刷新令牌的最长生命周期(单位:秒)
28 SlidingRefreshTokenLifetime int 刷新令牌滑动过期时间(单位:秒)
29 RefreshTokenUsage int 刷新令牌使用次数(0-有效期内刷新令牌不变 1-刷新令牌用后即焚)
30 UpdateAccessTokenClaimsOnRefresh bit 1
31 RefreshTokenExpiration int 刷新令牌过期方式(0-刷新令牌将在固定时间点到期(由AbsoluteRefreshTokenLifetime指定)1-刷新令牌时,将刷新刷新令牌的生命周期(按SlidingRefreshTokenLifetime中指定的数量)。生命周期不会超过AbsoluteRefreshTokenLifetime)
32 AccessTokenType int 授权token类型(0-Jwt;1-Reference)
33 EnableLocalLogin bit 1 登录UI是否呈现用户名/密码输入
34 IncludeJwtId bit 1
35 AlwaysSendClientClaims bit 1
36 ClientClaimsPrefix nvarchar 200 客户端声明类型前缀。默认为client_
37 PairWiseSubjectSalt nvarchar 200 此客户端用户在成对主体生成中使用的盐值
38 Created datetime2 创建时间
39 Updated datetime2 更新时间
40 LastAccessed datetime2 最后访问时间
41 UserSsoLifetime int 自上次用户身份验证以来的最长持续时间(单位:秒)
42 UserCodeType nvarchar 100 设备流用户代码的类型
43 DeviceCodeLifetime int 设备代码生存期(单位:秒)
44 NonEditable bit 1

2.2 ClientClaims 客户端声明配置

序号 列名 数据类型 长度 小数位数 主键 自增 允许空 默认值 列说明
1 Id int
2 Type nvarchar 250
3 Value nvarchar 250
4 ClientId int

2.3 ClientCorsOrigins 客户端跨域配置

序号 列名 数据类型 长度 小数位数 主键 自增 允许空 默认值 列说明
1 Id int
2 Origin nvarchar 150
3 ClientId int

2.4 ClientGrantTypes 客户端授权模式

序号 列名 数据类型 长度 小数位数 主键 自增 允许空 默认值 列说明
1 Id int
2 GrantType nvarchar 250
3 ClientId int

2.5 ClientIdPRestrictions(?这个我也不了解,项目中也没有用到)

序号 列名 数据类型 长度 小数位数 主键 自增 允许空 默认值 列说明
1 Id int
2 Provider nvarchar 200
3 ClientId int

2.6 ClientPostLogoutRedirectUris 客户端允许登出回跳地址

序号 列名 数据类型 长度 小数位数 主键 自增 允许空 默认值 列说明
1 Id int
2 PostLogoutRedirectUri nvarchar 2000 回跳地址,如:https://xxx/#/signout-callback-out,如果前端使用oidc登录,则需要配置
3 ClientId int

2.7 ClientProperties

序号 列名 数据类型 长度 小数位数 主键 自增 允许空 默认值 列说明
1 Id int
2 Key nvarchar 250
3 Value nvarchar 2000
4 ClientId int

2.8 ClientRedirectUris 客户端允许登录回跳地址

序号 列名 数据类型 长度 小数位数 主键 自增 允许空 默认值 列说明
1 Id int
2 RedirectUri nvarchar 2000 回跳地址,如:https://xxx/#/signin-callback-in,如果前端使用oidc登录,则需要配置
3 ClientId int

2.9 ClientScopes

序号 列名 数据类型 长度 小数位数 主键 自增 允许空 默认值 列说明
1 Id int
2 Scope nvarchar 200
3 ClientId int

2.10 ClientSecrets 客户端密钥

序号 列名 数据类型 长度 小数位数 主键 自增 允许空 默认值 列说明
1 Id int
2 Description nvarchar 2000 说明
3 Value nvarchar 4000 密钥(加密后)
4 Expiration datetime2 过期时间
5 Type nvarchar 250 类型(默认SharedSecret)
6 Created datetime2
7 ClientId int

三、接口资源(ApiResources)

接口资源模型图

3.1 ApiResources接口资源主表

序号 列名 数据类型 长度 小数位数 主键 自增 允许空 默认值 列说明
1 Id int
2 Enabled bit 1
3 Name nvarchar 200
4 DisplayName nvarchar 200
5 Description nvarchar 1000
6 AllowedAccessTokenSigningAlgorithms nvarchar 100
7 ShowInDiscoveryDocument bit 1
8 Created datetime2
9 Updated datetime2
10 LastAccessed datetime2
11 NonEditable bit 1

3.2 ApiResourceClaims接口资源声明表

序号 列名 数据类型 长度 小数位数 主键 自增 允许空 默认值 列说明
1 Id int
2 Type nvarchar 200
3 ApiResourceId int

3.3 ApiResourceProperties接口资源性能表(?还没搞明白用来干啥的)

序号 列名 数据类型 长度 小数位数 主键 自增 允许空 默认值 列说明
1 Id int
2 Key nvarchar 250
3 Value nvarchar 2000
4 ApiResourceId int

3.4 ApiResourceScopes

序号 列名 数据类型 长度 小数位数 主键 自增 允许空 默认值 列说明
1 Id int
2 Scope nvarchar 200
3 ApiResourceId int

3.5 ApiResourceSecrets

序号 列名 数据类型 长度 小数位数 主键 自增 允许空 默认值 列说明
1 Id int
2 Description nvarchar 1000
3 Value nvarchar 4000
4 Expiration datetime2
5 Type nvarchar 250
6 Created datetime2
7 ApiResourceId int

四、接口作用域(ApiScopes)

接口作用域模型图

4.1 ApiScopes

序号 列名 数据类型 长度 小数位数 主键 自增 允许空 默认值 列说明
1 Id int
2 Enabled bit 1
3 Name nvarchar 200
4 DisplayName nvarchar 200
5 Description nvarchar 1000
6 Required bit 1
7 Emphasize bit 1
8 ShowInDiscoveryDocument bit 1

4.2 ApiScopeClaims

序号 列名 数据类型 长度 小数位数 主键 自增 允许空 默认值 列说明
1 Id int
2 Type nvarchar 200
3 ScopeId int

4.3 ApiScopeProperties

序号 列名 数据类型 长度 小数位数 主键 自增 允许空 默认值 列说明
1 Id int
2 Key nvarchar 250
3 Value nvarchar 2000
4 ScopeId int

五、身份资源(IdentityResources)

身份资源模型图

5.1 IdentityResources

序号 列名 数据类型 长度 小数位数 主键 自增 允许空 默认值 列说明
1 Id int
2 Enabled bit 1
3 Name nvarchar 200
4 DisplayName nvarchar 200
5 Description nvarchar 1000
6 Required bit 1
7 Emphasize bit 1
8 ShowInDiscoveryDocument bit 1
9 Created datetime2
10 Updated datetime2
11 NonEditable bit 1

5.2 IdentityResourceClaims

序号 列名 数据类型 长度 小数位数 主键 自增 允许空 默认值 列说明
1 Id int
2 Type nvarchar 200
3 IdentityResourceId int

5.3 IdentityResourceProperties

序号 列名 数据类型 长度 小数位数 主键 自增 允许空 默认值 列说明
1 Id int
2 Key nvarchar 250
3 Value nvarchar 2000
4 IdentityResourceId int

六、DeviceCodes

序号 列名 数据类型 长度 小数位数 主键 自增 允许空 默认值 列说明
1 UserCode nvarchar 200
2 DeviceCode nvarchar 200
3 SubjectId nvarchar 200
4 SessionId nvarchar 100
5 ClientId nvarchar 200
6 Description nvarchar 200
7 CreationTime datetime2
8 Expiration datetime2
9 Data nvarchar -1

七、PersistedGrants

序号 列名 数据类型 长度 小数位数 主键 自增 允许空 默认值 列说明
1 Key nvarchar 200
2 Type nvarchar 50
3 SubjectId nvarchar 200
4 SessionId nvarchar 100
5 ClientId nvarchar 200
6 Description nvarchar 200
7 CreationTime datetime2
8 Expiration datetime2
9 ConsumedTime datetime2
10 Data nvarchar max

八、aspnet core使用IdentityServer4

8.1、生成证书文件

我这里使用的是OpenSSL进行生成的。

req -newkey rsa:2048 -nodes -keyout ids4.key -x509 -days 7300 -out ids4.cer
pkcs12 -export -in ids4.cer -inkey ids4.key -out ids4.pfx

8.2、写IdentityServer4持久化扩展方法

using IdentityServer4.EntityFramework.Stores;
using IdentityServer4.Stores;
using Ids4.Admin.IdentityServer.Validator;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using System;
using System.IO;
using System.Reflection;
using System.Security.Cryptography.X509Certificates;

namespace Ids4.Admin.IdentityServer.ConfigureServicesExtensions
{
    public static class ServiceCollectionExtension
    {
        public static IServiceCollection AddIdentityServerPrdExtension(this IServiceCollection services, IConfiguration configuration)
        {
            var dbServer = configuration["Apptions:DBServer"];
            return dbServer switch
            {
                "mssql" => AddIdentityServerPrdExtension_MsSql(services, configuration),
                "mysql" => AddIdentityServerPrdExtension_MySql(services, configuration),
                _ => AddIdentityServerPrdExtension_MsSql(services, configuration),
            };
        }

        #region IdentityServer4 配置 MsSql版

        /// 
        /// 注册生产环境IdentityServer4配置
        /// 
        /// 
        /// 
        private static IServiceCollection AddIdentityServerPrdExtension_MsSql(IServiceCollection services, IConfiguration configuration)
        {
            //读取证书,我这里使用的pfx证书,可以使用openssl
            var fileName = Path.Combine(AppDomain.CurrentDomain.SetupInformation.ApplicationBase, configuration["PfxSettings:Path"]);
            if (!File.Exists(fileName))
            {
                throw new FileNotFoundException("签名证书文件未找到!");
            }
            var cert = new X509Certificate2(fileName, configuration["PfxSettings:Pwd"]);
            var connectionString = configuration.GetConnectionString("DefaultConnection");

            var migrationsAssembly = typeof(Startup).GetTypeInfo().Assembly.GetName().Name;
            services.AddIdentityServer()
                .AddSigningCredential(cert)
                //客户端及资源数据库存储配置
                .AddConfigurationStore(options =>
                {
                    options.ConfigureDbContext = context =>
                        context.UseSqlServer(connectionString,
                            sql => sql.MigrationsAssembly(migrationsAssembly));
                })
                //令牌及授权码数据库存储配置
                .AddOperationalStore(options =>
                {
                    options.ConfigureDbContext = context =>
                        context.UseSqlServer(connectionString,
                            sql => sql.MigrationsAssembly(migrationsAssembly));

                    // 自动清理过期token.
                    options.EnableTokenCleanup = true;
                })
                .AddConfigurationStoreCache()//这里使用了内存缓存,避免每次授权都要进行读库
                .AddResourceOwnerValidator()//这里是我实现的自定义用户名密码登陆器
                .AddExtensionGrantValidator();//这里是我实现的自定义微信openId登陆器

            services.AddTransient();
            return services;
        }

        #endregion

        #region IdentityServer4 配置 MySql版

        /// 
        /// 注册生产环境IdentityServer4配置
        /// 
        /// 
        /// 
        private static IServiceCollection AddIdentityServerPrdExtension_MySql(IServiceCollection services, IConfiguration configuration)
        {
            var fileName = Path.Combine(AppDomain.CurrentDomain.SetupInformation.ApplicationBase, configuration["PfxSettings:Path"]);
            if (!File.Exists(fileName))
            {
                throw new FileNotFoundException("签名证书文件未找到!");
            }
            var cert = new X509Certificate2(fileName, configuration["PfxSettings:Pwd"]);
            var connectionString = configuration.GetConnectionString("MySqlConnection");

            var migrationsAssembly = typeof(Startup).GetTypeInfo().Assembly.GetName().Name;
            services.AddIdentityServer()
                .AddSigningCredential(cert)
                .AddConfigurationStore(options =>
                {
                    options.ConfigureDbContext = context =>
                        context.UseMySql(connectionString, new MySqlServerVersion(new Version("5.7")),
                            sql => sql.MigrationsAssembly(migrationsAssembly));
                })
                .AddOperationalStore(options =>
                {
                    options.ConfigureDbContext = context =>
                        context.UseMySql(connectionString, new MySqlServerVersion(new Version("5.7")),
                            sql => sql.MigrationsAssembly(migrationsAssembly));

                    // 自动清理过期token.
                    options.EnableTokenCleanup = true;
                })
                .AddConfigurationStoreCache()//这里使用了内存缓存,避免每次授权都要进行读库
                .AddResourceOwnerValidator()
                .AddExtensionGrantValidator(); 

            services.AddTransient();
            return services;
        }

        #endregion
    }
}
  public void ConfigureServices(IServiceCollection services)
   {
            services.AddIdentityServerPrdExtension(Configuration);//注册IdentityServer4持久化
   }

8.3、自定义登录验证器

8.3.1 密码模式验证器

官方或者各大百度找到的示例,几乎都是使用IdentityServer4的TestUser,在生产中,这种写法肯定不行。上述代码中有LoginValidator,这个就是我自定义的密码登录验证器,主要功能就是替换默认的密码模式登录。

/// 
/// 密码模式验证器
/// 
/// 需要继承IResourceOwnerPasswordValidator,在注册IdentityServer4时,通过AddResourceOwnerValidator注册验证器
public class LoginValidator : IResourceOwnerPasswordValidator
{
    private readonly ISystemService _systemService;

    public LoginValidator(ISystemService systemService)
    {
        _systemService = systemService;
    }

    public async Task ValidateAsync(ResourceOwnerPasswordValidationContext context)
    {
        //用户密码登录,这个可以自己实现
        var (userInfo, errMsg) = await _systemService.LgoinAsync(new LoginReques()
        {
            Account = context.UserName,
            Password = context.Password,
        });
        context.Result = UserGrantResult.GetGrantValidationResult(userInfo, errMsg);
    }
}

public class UserGrantResult
{
    public static GrantValidationResult GetGrantValidationResult(LoginResponse user, string errMsg = "")
    {
        if (user != null)
        {
            Claim[] claims = new Claim[] {
                new Claim("employeeId",user.EmployeeId.ToString()),
                new Claim("tenantId",user.TenantId.ToString()),
                //这里可以写自己所需的claim
            };
            return new GrantValidationResult(subject: user.EmployeeId.ToString(), authenticationMethod: "custom", claims: claims);
        }
        else
        {
            return new GrantValidationResult(TokenRequestErrors.InvalidRequest, string.IsNullOrWhiteSpace(errMsg) ? "用户名或密码错误" : errMsg);
        }
    }
}

8.3.2 自定义微信登录模式

有些业务可能会需要微信一键登录吧,IdentityServer4可以通过自定义扩展授权模式,来实现微信openId的一键登录

    /// 
    /// 微信授权验证
    /// 
    /// 需要继承IExtensionGrantValidator,在注册IdentityServer4时,通过AddExtensionGrantValidator注册验证器
    public class WeiXinOpenGrantValidator : IExtensionGrantValidator
    {
        private readonly IUserService _userService;

        public WeiXinOpenGrantValidator(IUserService userService)
        {
            _userService = userService;
        }

        public string GrantType => "wechatopen";//定义微信登录模式名称

        /// 
        /// 验证器
        /// 
        /// 
        /// 
        public async Task ValidateAsync(ExtensionGrantValidationContext context)
        {
            try
            {
                #region 获取参数
                var appid = context.Request.Raw["appid"];//微信AppId
                var openId = context.Request.Raw["openid"];//用户OpenId
                var phone = context.Request.Raw["phone"];//用户手机号码
                #endregion

                var (userInfo, errMsg) = await _userService.QueryAccountByWechatOpenIdAsync(appid, openId, phone);//自定义微信登录方法
                context.Result = UserGrantResult.GetGrantValidationResult(userInfo, errMsg);
            }
            catch (Exception ex)
            {
                context.Result = new GrantValidationResult()
                {
                    IsError = true,
                    Error = ex.Message
                };
            }
        }
    }

参考8.2,注册IdentityServer4持久化时,将自定义验证器也注册进去就行。

8.4 客户端使用IdentityServer4

8.4.1 API使用IdentityServer4

public static IServiceCollection AddIds4Client_API(this IServiceCollection services, ApiAuthenticationModel model)
{
        ervices.AddAuthorization();
        ervices.AddAuthentication("Bearer")
            .AddIdentityServerAuthentication(options =>
            {
                /*===========================================
                这里填的是签名颁发机构地址,会调用//.well-known/openid-configuration/jwks 获取签名服务器公钥,
                是通过访问/.well-known/openid-configuration,获取到所需信息
                ===========================================*/
                options.Authority = model.Authority;
                options.RequireHttpsMetadata = model.RequireHttpsMetadata;           //是否开启https    
                options.ApiName = model.ApiName;  //api的name,
                options.ApiSecret = model.ApiSecret;//api密钥
            });
        return services;
}

    public class ApiAuthenticationModel
    {
        /// 
        /// 验证地址
        /// 
        public string Authority { get; set; }

        /// 
        /// 是否Https请求
        /// 
        public bool RequireHttpsMetadata { get; set; }

        /// 
        /// 客户端ID名称
        /// 
        public string ApiName { get; set; }

        /// 
        /// 客户端密钥
        /// 
        public string ApiSecret { get; set; }
    }

接下来会开一篇新的文章,讲述各种IdentityServer4配置关系

你可能感兴趣的:(IdentityServer4(二)持久化)