zoukankan      html  css  js  c++  java
  • IdentitiServser4 + Mysql实现Authorization Server

     Identity Server 4官方文档:https://identityserver4.readthedocs.io/en/latest/

    新建2个asp.net core 项目使用空模板

    Auth 身份认证服务

    Client客户端

    Auth项目打开安装相关依赖 

    IdentityServer4 和 EF 实体框架

    Mysql EF Provider

    Nlog日志组件

    打开Startup.cs 文件配置 管道

    配置 identity server

    还是Startup.cs,编辑ConfigureServices方法:

    添加Nlog日志

      services.AddLogging(logBuilder =>
                {
                    logBuilder.AddNLog();
                });

    日志配置文件(右键属性选择始终复制)

    日志配置内容

    <?xml version="1.0" encoding="utf-8" ?>
    <nlog xmlns="http://www.nlog-project.org/schemas/NLog.xsd"
          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
      <extensions>
        <add assembly="Nlog.Targets.Splunk"/>
      </extensions>
    
      <targets async="true">
        <target name="asyncFile" xsi:type="File"
                layout="[${longdate}] [${level}] [${logger}] [${message}] ${newline} ${exception:format=tostring}"
                fileName="${basedir}/log/${shortdate}.txt"
                archiveFileName="${basedir}/log/archives/log.{#####}.txt"
                archiveAboveSize="102400000"
                archiveNumbering="Sequence"
                concurrentWrites="true"
                keepFileOpen="false"
                encoding="utf-8" />
    
        
    
        <target name="console" xsi:type="console"/>
      </targets>
    
      <rules>
        <!--Info,Error,Warn,Debug,Fatal-->
        <logger name="*" levels="Info,Error,Warn,Debug,Fatal" writeTo="asyncFile" />
        <logger name="*" minlevel="Error" writeTo="console" />
    
      </rules>
    </nlog>

    添加IdentityServer服务

    services.AddIdentityServer()

    添加身份验证方式

      services.AddIdentityServer()
                    .AddResourceOwnerValidator<ResourceOwnerPasswordValidator>()

    ResourceOwnerPasswordValidator 类实现接口 IResourceOwnerPasswordValidator

    实现验证ValidateAsync异步方法

     public async Task ValidateAsync(ResourceOwnerPasswordValidationContext context)
            {
                try
                {
                    //根据context.UserName和context.Password与数据库的数据做校验,判断是否合法
                    if (context.UserName == "wjk" && context.Password == "123")
                    {
                        context.Result = new GrantValidationResult(
                            subject: context.UserName,
                            authenticationMethod: "custom",
                            claims: GetUserClaims());
                    }
                    else
                    {
    
                        //验证失败
                        context.Result = new GrantValidationResult(TokenRequestErrors.InvalidGrant, "invalid custom credential");
                    }
                }
                catch (System.Exception exception)
                {
                    context.Result = new GrantValidationResult(TokenRequestErrors.InvalidGrant, "Invalid username or password");
                    throw;
                }
            }

    设置Claims

    private static IEnumerable<Claim> GetUserClaims()
            {
                return new[]
                {
                    new Claim("uid", "1"),
                    new Claim(JwtClaimTypes.Name,"wjk"),
                    new Claim(JwtClaimTypes.GivenName, "GivenName"),
                    new Claim(JwtClaimTypes.FamilyName, "yyy"),
                    new Claim(JwtClaimTypes.Email, "977865769@qq.com"),
                    new Claim(JwtClaimTypes.Role,"admin")
                };
            }

    设置验证方式

     services.AddIdentityServer()
                    .AddResourceOwnerValidator<ResourceOwnerPasswordValidator>()
                    .AddSigningCredential(cert)

    使用pfx 证书验证

    cert证书读取

     var certAbsolutePath = PathResolver.ResolveCertConfigPath(Configuration["AuthOption:X509Certificate2FileName"], Environment);
     var cert = new X509Certificate2(certAbsolutePath, Configuration["AuthOption:X509Certificate2Password"]);
    public static class PathResolver
        {
            public static string ResolveCertConfigPath(string configPath, IHostingEnvironment environment)
            {
                var start = new[] { "./", ".\", "~/", "~\" };
                if (start.Any(d => configPath.StartsWith(d)))
                {
                    foreach (var item in start)
                    {
                        configPath = configPath.TrimStart(item);
                    }
                    return Path.Combine(environment.ContentRootPath, configPath);
                }
                return configPath;
            }
    
            public static string TrimStart(this string target, string trimString)
            {
                if (string.IsNullOrEmpty(trimString)) return target;
    
                string result = target;
                while (result.StartsWith(trimString))
                {
                    result = result.Substring(trimString.Length);
                }
    
                return result;
            }
        }

    appsettings.json配置文件

      "AuthOption": {
        "X509Certificate2FileName": "./work.pfx",
        "X509Certificate2Password": "123456"
      }

    证书生成方式:https://www.cnblogs.com/liuxiaoji/p/10790057.html

    EF注入

    var migrationsAssembly = typeof(Startup).GetTypeInfo().Assembly.GetName().Name;
     services.AddIdentityServer()
                    .AddResourceOwnerValidator<ResourceOwnerPasswordValidator>()
                    .AddSigningCredential(cert)
                    .AddConfigurationStore(options =>
                    {
                        options.ConfigureDbContext = b =>
                            b.UseMySql(connectionString, sql => sql.MigrationsAssembly(migrationsAssembly));
                    }).AddOperationalStore(options =>
                    {
                        options.ConfigureDbContext = b =>
                            b.UseMySql(connectionString, sql => sql.MigrationsAssembly(migrationsAssembly));
                        options.EnableTokenCleanup = true;
                    });

    数据库配置初始化

      /// <summary>
        /// 配置
        /// </summary>
        public class Config
        {
            /// <summary>
            /// 定义身份资源
            /// </summary>
            /// <returns>身份资源</returns>
            public static IEnumerable<IdentityResource> GetIdentityResources()
            {
                return new IdentityResource[]
                {
                    //身份资源是用户的用户ID
                    new IdentityResources.OpenId()
                };
            }
    
            /// <summary>
            /// 定义API资源
            /// </summary>
            /// <returns>API资源</returns>
            public static IEnumerable<ApiResource> GetApiResources()
            {
                return new[]
                {
                    new ApiResource("api1", new[] {"uid", JwtClaimTypes.Name}),
                    new ApiResource("api2", new[] { "uid", "name"})
                };
            }
    
            /// <summary>
            /// 定义客户端
            /// </summary>
            /// <returns>客户端</returns>
            public static IEnumerable<Client> GetClients()
            {
                return new[]
                {
                    new Client
                    {
                        ClientId = "client",
                        AllowedGrantTypes = GrantTypes.ResourceOwnerPasswordAndClientCredentials,
                        AllowOfflineAccess = true,
                        ClientSecrets = {new Secret("secret".Sha256())},
                        AllowedScopes = {OidcConstants.StandardScopes.OfflineAccess, "api1", "api2"}
                    }
                };
            }
        }
     private void InitializeDatabase(IApplicationBuilder app)
            {
                using (var serviceScope = app.ApplicationServices.GetService<IServiceScopeFactory>().CreateScope())
                {
                    /*
                     *切换到程序包管理器控制器,将默认项目设置为Auth,然后输入以下命令
                     *PersistedGrantDbContext
                     *Add-Migration -Name InitialIdentityServerConfigurationDbMigration -Context PersistedGrantDbContext -OutputDir Migrations/IdentityServer/PersistedGrantDb -Project Auth
                     *ConfigurationDbContext
                     *Add-Migration -Name InitialIdentityServerConfigurationDbMigration -Context ConfigurationDbContext -OutputDir Migrations/IdentityServer/ConfigurationDb -Project Auth
                     *命令将使用ConfigurationDbContext创建一个名为InitialIdentityServerConfigurationDbMigration 的迁移。迁移文件将输出到将默认项目设置为Auth项目的Migrations/IdentityServer/ConfigurationDb文件夹。
                    */
                    // 必须执行数据库迁移命令才能生成数据库表
                    serviceScope.ServiceProvider.GetRequiredService<PersistedGrantDbContext>().Database.Migrate();
                    var context = serviceScope.ServiceProvider.GetRequiredService<ConfigurationDbContext>();
                    context.Database.Migrate();
                    if (!context.Clients.Any())
                    {
                        foreach (var client in Config.GetClients())
                        {
                            context.Clients.Add(client.ToEntity());
                        }
                        context.SaveChanges();
                    }
    
                    if (!context.IdentityResources.Any())
                    {
                        foreach (var resource in Config.GetIdentityResources())
                        {
                            context.IdentityResources.Add(resource.ToEntity());
                        }
                        context.SaveChanges();
                    }
    
                    if (!context.ApiResources.Any())
                    {
                        foreach (var resource in Config.GetApiResources())
                        {
                            context.ApiResources.Add(resource.ToEntity());
                        }
                        context.SaveChanges();
                    }
                }
            }
            public void Configure(IApplicationBuilder app, IHostingEnvironment env)
            {
                InitializeDatabase(app);
                app.UseIdentityServer();
            }

    EF数据库迁移

     var connectionString = Configuration.GetConnectionString("Auth");
     "ConnectionStrings": {
        "Auth": "Server=localhost;database=auth;uid=root;pwd=123456;charset=UTF8;"
      },

    执行命令

     切换到程序包管理器控制器,将默认项目设置为Auth,然后输入以下命令
     PersistedGrantDbContext
     Add-Migration -Name InitialIdentityServerConfigurationDbMigration -Context PersistedGrantDbContext -OutputDir Migrations/IdentityServer/PersistedGrantDb -Project Auth
     ConfigurationDbContext
     Add-Migration -Name InitialIdentityServerConfigurationDbMigration -Context ConfigurationDbContext -OutputDir Migrations/IdentityServer/ConfigurationDb -Project Auth
     命令将使用ConfigurationDbContext创建一个名为InitialIdentityServerConfigurationDbMigration 的迁移。迁移文件将输出到将默认项目设置为Auth项目的Migrations/IdentityServer/ConfigurationDb文件夹。

     

    Client打开添加相关配置

    IdentityServer4 和 EF 实体框架

    Nlog日志组件

    类型转化框架

    打开Startup.cs 文件配置 管道

            public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
            {
                //添加日志
                loggerFactory.AddNLog();
    
                //添加权限验证
                app.UseAuthentication();
    
                //添加MVC框架
                app.UseMvc();
            }
      public void ConfigureServices(IServiceCollection services)
            {
                //加入HttpClient
                services.AddHttpClient();
    
                var config = Configuration.GetSection("JwtBearerOptions").Get<JwtBearerOptions>();
                services.Configure<JwtBearerOptions>(Configuration.GetSection("JwtBearerOptions"));
                services.AddAuthentication(x =>
                {
                    x.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
                    x.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
                }).AddJwtBearer(option =>
                {
                    option.RequireHttpsMetadata = false;
                    option.Authority = config.Authority;
                    option.RequireHttpsMetadata = config.RequireHttpsMetadata;
                    option.Audience = config.Audience;
                });
    
                services.AddMvc()
                    .SetCompatibilityVersion(CompatibilityVersion.Version_2_2)
                    .AddJsonOptions(option => { option.UseCamelCasing(true); });
            }

    配置文件

      "JwtBearerOptions": {
        "Authority": "http://localhost:61491",
        "RequireHttpsMetadata": false,
        "Audience": "api1"
      }

    添加AccountController 用于登录 和刷新Toekn

        [AllowAnonymous]
        [Route("api/[controller]")]
        public class AccountController: ControllerBase
        {
            /// <summary>
            /// http客户端
            /// </summary>
            private readonly HttpClient _client;
    
            /// <summary>
            /// 日志
            /// </summary>
            private readonly ILogger<AccountController> _logger;
    
            /// <summary>
            /// 身份认证配置
            /// </summary>
            private readonly JwtBearerOptions _authOption;
    
            public AccountController(
                IHttpClientFactory httpClientFactory,
                ILogger<AccountController> logger,
                IOptions<JwtBearerOptions> authOptions)
            {
                _client = httpClientFactory.CreateClient();
                _logger = logger;
                _authOption = authOptions.Value;
            }
    
            [NonAction]
            private async Task<LoginResult> GenerateTokenAsync(string account, string password, string enterpriseId)
            {
                var disco = await _client.GetDiscoveryDocumentAsync(new DiscoveryDocumentRequest
                {
                    Address = _authOption.Authority,
                    Policy =
                    {
                        RequireHttps = false
                    }
                });
                if (disco.IsError)
                {
                    var msg = $"用户{account}登陆到注册中心出错,错误码{disco.StatusCode},详情{disco.Json}";
                    _logger.LogError(msg);
                    throw new InvalidOperationException(msg);
                }
    
                var parameters = new Dictionary<string, string>
                {
                    { ClaimsConst.EnterpriseId, enterpriseId }
                };
    
                // request token
                var tokenResponse = await _client.RequestPasswordTokenAsync(new PasswordTokenRequest
                {
                    Address = disco.TokenEndpoint,
                    ClientId = "client",
                    ClientSecret = "secret",
                    UserName = account,
                    Password = password,
                    Parameters = parameters
                });
    
                if (tokenResponse.IsError)
                {
                    var msg = $"用户{account}获取token出错,错误码{tokenResponse.HttpStatusCode},详情{tokenResponse.Json}";
                    _logger.LogError(msg);
                    throw new InvalidOperationException(msg);
                }
    
                return new LoginResult
                {
                    AccessToken = tokenResponse.AccessToken,
                    RefreshToken = tokenResponse.RefreshToken,
                    TokenType = tokenResponse.TokenType,
                    ExpiresIn = tokenResponse.ExpiresIn
                };
            }
    
            /// <summary>
            /// 登录
            /// </summary>
            /// <param name="login">登录信息</param>
            /// <returns>结果</returns>
            [HttpPost("login")]
            public async Task<ActionResult<LoginResult>> Login([FromBody]Login login)
            {
                var result = await GenerateTokenAsync(login.Account, login.Password, "自定义参数");
                if (result != null)
                {
                    return Ok(result);
                }
                return Unauthorized();
            }
    
            /// <summary>
            /// 用refresh token获取新的access token
            /// </summary>
            /// <param name="token">refresh token</param>
            /// <returns></returns>
            [HttpGet("refresh/{token}")]
            public async Task<ActionResult<LoginResult>> Refresh(string token)
            {
                var disco = await _client.GetDiscoveryDocumentAsync(new DiscoveryDocumentRequest
                {
                    Address = _authOption.Authority,
                    Policy =
                    {
                        RequireHttps = false
                    }
                });
                if (disco.IsError)
                {
                    _logger.LogError($"获取刷新token出错,错误码{disco.StatusCode},详情{disco.Json}");
                    return null;
                }
    
                var tokenResponse = await _client.RequestRefreshTokenAsync(new RefreshTokenRequest
                {
                    Address = disco.TokenEndpoint,
                    ClientId = "client",
                    ClientSecret = "secret",
                    RefreshToken = token,
                    Scope = OidcConstants.StandardScopes.OfflineAccess,
                });
    
                if (tokenResponse.IsError)
                {
                    _logger.LogError($"获取刷新token出错,错误码{tokenResponse.HttpStatusCode},详情{tokenResponse.Json}");
                    return Unauthorized(new  { Code = 1, Msg = "刷新token已过期" });
                }
    
                var tokenResult = tokenResponse.MapTo<LoginResult>();
                return Ok(tokenResult);
            }
        }

    添加 AuthController 用于测试token 身份验证

        [Authorize]
        [Route("api/[controller]")]
        public class AuthController : ControllerBase
        {
            public ActionResult<string> Get()
            {
                return Ok("授权成功");
            }
        }

    相关实体类

      public class ClaimsConst
        {
            public const string UserId = "uid";
            public const string UserName = "uname";
            public const string EnterpriseId = "eid";
            public const string DepartmentId = "did";
            public const string Subject = "sub";
            public const string Expires = "exp";
            public const string Issuer = "iss";
            public const string Audience = "aud";
            public const string IssuedAt = "iat";
            public const string ClientId = "client_id";
            public const string LoginStrategy = "lgs";
            public const string AuthTime = "auth_time";
            public const string Idp = "idp";
            public const string NotBefore = "nbf";
            public const string UserData = "udata";
            public const string DeviceCode = "dcd";
        }
        public class Login
        {
            /// <summary>
            /// 帐号
            /// </summary>
            [Required(ErrorMessage = "帐号不能为空")]
            [MaxLength(20, ErrorMessage = "帐号长度最长为20位字符")]
            public string Account { get; set; }
    
            /// <summary>
            /// 密码
            /// </summary>
            [Required(ErrorMessage = "密码不能为空")]
            [MaxLength(40, ErrorMessage = "密码长度最长为40位字符")]
            public string Password { get; set; }
        }
        public class LoginResult
        {
            /// <summary>
            /// token
            /// </summary>
            public string AccessToken { get; set; }
    
            /// <summary>
            /// 刷新token
            /// </summary>
            public string RefreshToken { get; set; }
    
            /// <summary>
            /// token过期时间
            /// </summary>
            public int ExpiresIn { get; set; }
    
            /// <summary>
            /// token类型
            /// </summary>
            public string TokenType { get; set; }
        }

    使用postman 测试

    登录

    刷新token

    访问API

    使用正确的Token

  • 相关阅读:
    Sql Server 2008卸载后再次安装一直报错
    listbox 报错 Cannot have multiple items selected when the SelectionMode is Single.
    Sql Server 2008修改Sa密码
    学习正则表达式
    Sql Server 查询第30条数据到第40条记录数
    Sql Server 复制表
    Sql 常见面试题
    Sql Server 简单查询 异步服务器更新语句
    jQuery stop()用法以及案例展示
    CSS3打造不断旋转的CD封面
  • 原文地址:https://www.cnblogs.com/liuxiaoji/p/10795810.html
Copyright © 2011-2022 走看看