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

  • 相关阅读:
    怎样快速学会ZBrush 中的移动笔刷的运用
    ZBrush中如何才能快速完成脸部雕刻(下)
    ZBrush中如何才能快速完成脸部雕刻(上)
    ZBrush中的Clip剪切笔刷怎么快速运用
    ZBrush中必须记住的常用快捷键
    怎么在ZBrush中渲染漫画风格的插画
    怎么运用ZBrush中的Z球制作身体部分
    ZBrush中的笔刷该怎样制作
    如何在ZBrush中添加毛发
    App交互demo
  • 原文地址:https://www.cnblogs.com/liuxiaoji/p/10795810.html
Copyright © 2011-2022 走看看