一、前言查阅了大多数相关资料,查阅到的IdentityServer4 的相关文章大多是比较简单并且多是翻译官网的文档编写的,我这里在 这里就直接开始撸代码,概念性东西就已经不概述了,想要了解概念推荐大家查看我之前的文章和官方文档: 二、应用实战2.1 模拟场景最初小团队的电商系统场景如下图: 这张架构图缺点:
大多数小电商团队对于多客户端登录授权来说可能已经实现了Oauth 2.0 的身份授权验证,但是是和电商业务集成在一个网关里面,这样不是很好的方式;由于公司业务横向扩大,产品经理调研了代理商业务,最终让技术开发代理商业务系统。架构师出于后续发展的各方面考虑,把代理商业务单独建立了一个独立的网关,并且把授权服务一并给独立出来,调整后的电商系统架构图如下: 身份授权从业务系统中拆分出来后,有了如下的优势:
代理商业务引入进来后,同时又增加了秒杀活动,发现成交量大大增大,支付订单集中在某一时刻翻了十几倍,这时候整个电商业务API网关已经扛不住了,负载了几台可能也有点吃力;开发人员经过跟架构师一起讨论,得出了扛不住的原因:主要是秒杀活动高并发的支付,以至于整个电商业务系统受到影响,故准备把支付系统从业务系统中拆分出成独立的支付网关,并做了一定的负载,成功解决了以上问题,这时候整个电商系统架构图就演变成如下: 支付网关服务抽离后的优势:
上面的电商网关演变架构图中我这里没有画出具体的请求流向,偷了个赖,这里还是先把OAuth2.0 的授权大体的流程图单独贴出来: 由于 2.2 IdentityServer4 密码授权模式授权网关服务静态内存配置方式
分资源分为身份资源( /// <summary> /// Api资源 静态方式定义 /// </summary> /// <returns></returns> public static IEnumerable<ApiResource> GetApiResources() { return new List<ApiResource> { new ApiResource(OAuthConfig.UserApi.ApiName,OAuthConfig.UserApi.ApiName), }; }
接下来 public static IEnumerable<Client> GetClients() { return new List<Client> { new Client() { ClientId =OAuthConfig.UserApi.ClientId, AllowedGrantTypes = new List<string>() { GrantTypes.ResourceOwnerPassword.FirstOrDefault(),//Resource Owner Password模式 }, ClientSecrets = {new Secret(OAuthConfig.UserApi.Secret.Sha256()) }, AllowedScopes= {OAuthConfig.UserApi.ApiName}, AccessTokenLifetime = OAuthConfig.ExpireIn, }, }; }
代码中可以看到有一个 public class OAuthConfig { /// <summary> /// 过期秒数 /// </summary> public const int ExpireIn = 36000; /// <summary> /// 用户Api相关 /// </summary> public static class UserApi { public static string ApiName = "user_api"; public static string ClientId = "user_clientid"; public static string Secret = "user_secret"; } } 如果后续架构升级,添加了其他的网关服务,则只需要在这里添加所需要保护的API 资源,也可以通过读取数据库方式读取受保护的Api资源。 接下来 /// <summary> /// 测试的账号和密码 /// </summary> /// <returns></returns> public static List<TestUser> GetTestUsers() { return new List<TestUser> { new TestUser() { SubjectId = "1", Username = "test", Password = "123456" } }; } 上面受保护的资源,和客户端以及测试账号都已经建立好了,现在需要把IdentityServer4 注册到DI中: public void ConfigureServices(IServiceCollection services) { services.AddControllers(); #region 内存方式 services.AddIdentityServer() .AddDeveloperSigningCredential() .AddInMemoryApiResources(OAuthMemoryData.GetApiResources()) .AddInMemoryClients(OAuthMemoryData.GetClients()) .AddTestUsers(OAuthMemoryData.GetTestUsers()); #endregion } 代码解读:
最后通过 public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } app.UseIdentityServer(); app.UseRouting(); app.UseAuthorization(); app.UseEndpoints(endpoints => { endpoints.MapControllers(); }); } 好了,现在 电商用户网关Api项目现在我来新建一个WebApi 大的用户网关服务项目,取名为 [ApiController] [Route("[controller]")] public class WeatherForecastController : ControllerBase { private static readonly string[] Summaries = new[] { "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching" }; private readonly ILogger<WeatherForecastController> _logger; public WeatherForecastController(ILogger<WeatherForecastController> logger) { _logger = logger; } [HttpGet] public IEnumerable<WeatherForecast> Get() { var rng = new Random(); return Enumerable.Range(1, 5).Select(index => new WeatherForecast { Date = DateTime.Now.AddDays(index), TemperatureC = rng.Next(-20, 55), Summary = Summaries[rng.Next(Summaries.Length)] }) .ToArray(); } } 接下来在 public void ConfigureServices(IServiceCollection services) { services.AddControllers(); services.AddAuthorization(); services.AddAuthentication("Bearer") .AddIdentityServerAuthentication(options => { options.Authority = "http://localhost:5000"; //配置Identityserver的授权地址 options.RequireHttpsMetadata = false; //不需要https options.ApiName = OAuthConfig.UserApi.ApiName; //api的name,需要和config的名称相同 }); } 这里的 接下来需要把授权和认证中间件分别注册到Http 管道中,代码如下: public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } app.UseRouting(); app.UseAuthentication(); app.UseAuthorization(); app.UseEndpoints(endpoints => { endpoints.MapControllers(); }); } 现在授权服务网关启用已经完成,只需要在需要保护的 现在我通过postman 工具来单独访问 用户网关服务API,不携带任何信息的情况下,如图: 我们接下来再来访问授权服务网关,如图: 请求网关服务中body中携带了用户名及密码等相关信息,这是返回了 访问结果中已经返回了我们所需要的接口数据,大家目前已经对密码模式的使用有了一定的了解,但是这时候可能会有人问我,我生产环境中可能需要通过数据库的方式进行用户信息的判断,以及客户端授权方式需要更加灵活的配置,可通过后台来配置ClientId以及授权方式等,那应该怎么办呢?下面我再来给大家带来生存环境中的实现方式。 数据库匹配验证方式我们需要通过用户名和密码到数据库中验证方式则需要实现 public class ResourceOwnerPasswordValidator : IResourceOwnerPasswordValidator { public async Task ValidateAsync(ResourceOwnerPasswordValidationContext context) { try { var userName = context.UserName; var password = context.Password; //验证用户,这么可以到数据库里面验证用户名和密码是否正确 var claimList = await ValidateUserAsync(userName, password); // 验证账号 context.Result = new GrantValidationResult ( subject: userName, authenticationMethod: "custom", claims: claimList.ToArray() ); } catch (Exception ex) { //验证异常结果 context.Result = new GrantValidationResult() { IsError = true, Error = ex.Message }; } } #region Private Method /// <summary> /// 验证用户 /// </summary> /// <param name="loginName"></param> /// <param name="password"></param> /// <returns></returns> private async Task<List<Claim>> ValidateUserAsync(string loginName, string password) { //TODO 这里可以通过用户名和密码到数据库中去验证是否存在, // 以及角色相关信息,我这里还是使用内存中已经存在的用户和密码 var user = OAuthMemoryData.GetTestUsers(); if (user == null) throw new Exception("登录失败,用户名和密码不正确"); return new List<Claim>() { new Claim(ClaimTypes.Name, $"{loginName}"), }; } #endregion } 用户密码验证器已经实现完成,现在需要把之前的通过 public void ConfigureServices(IServiceCollection services) { services.AddControllers(); #region 数据库存储方式 services.AddIdentityServer() .AddDeveloperSigningCredential() .AddInMemoryApiResources(OAuthMemoryData.GetApiResources()) .AddInMemoryClients(OAuthMemoryData.GetClients()) //.AddTestUsers(OAuthMemoryData.GetTestUsers()); .AddResourceOwnerValidator<ResourceOwnerPasswordValidator>(); #endregion } 目前已经实现了用户名和密码数据库验证的方式,但是现在有人会考虑另外一个场景,客户端的授权方式等也需要通过后台可配置的方式,这样比较灵活,不通过代码中静态配置的方式,那应该这么办呢? public class ClientStore : IClientStore { public async Task<Client> FindClientByIdAsync(string clientId) { #region 用户名密码 var memoryClients = OAuthMemoryData.GetClients(); if (memoryClients.Any(oo => oo.ClientId == clientId)) { return memoryClients.FirstOrDefault(oo => oo.ClientId == clientId); } #endregion #region 通过数据库查询Client 信息 return GetClient(clientId); #endregion } private Client GetClient(string client) { //TODO 根据数据库查询 return null; } }
public void ConfigureServices(IServiceCollection services) { services.AddControllers(); #region 数据库存储方式 services.AddIdentityServer() .AddDeveloperSigningCredential() .AddInMemoryApiResources(OAuthMemoryData.GetApiResources()) //.AddInMemoryClients(OAuthMemoryData.GetClients()) .AddClientStore<ClientStore>() .AddResourceOwnerValidator<ResourceOwnerPasswordValidator>(); #endregion } 好了数据库查询匹配方式也已经改造完了,业务网关服务不需要改动如何代码,运行结果这里就不在运行演示了。Demo 代码已经上传到github 上了,github 源代码 结语:通过IdentityServer4 实现的简单授权中心的思想也就完成了,后续继续学习,有错误地方还请留言指出!感谢!!! |
|