Asp.Net Core 中IdentityServer4 授权中心之应用实战

一、前言

查阅了大多数相关资料,搜索到的IdentityServer4 的应用文章大多是比较简单并且多是翻译官网的文档编写的,我这里在 Asp.Net Core 中IdentityServer4 的应用分析中会以一个电商系统架构升级过程中普遍会遇到的场景进行实战性讲述分析,同时最后会把我的实战性的代码放到github 上,敬请大家关注!

这里就直接开始撸代码,概念性东西就已经不概述了,想要了解概念推荐大家查看我之前的文章和官方文档:

  • Asp.Net Core IdentityServer4 中的基本概念

  • IdentityServer4 官方文档

二、应用实战

2.1 模拟场景

最初小团队的电商系统场景如下图:

这张架构图缺点:

•发布频繁,发布影响整个电商系统•很难做到敏捷开发•维护性可能会存在一定的弊端,主要看内部架构情况。

大多数小电商团队对于多客户端登录授权来说可能已经实现了Oauth 2.0 的身份授权验证,但是是和电商业务集成在一个网关里面,这样不是很好的方式;由于公司业务横向扩大,产品经理调研了代理商业务,最终让技术开发代理商业务系统。架构师出于后续发展的各方面考虑,把代理商业务单独建立了一个独立的网关,并且把授权服务一并给独立出来,调整后的电商系统架构图如下:

身份授权从业务系统中拆分出来后,有了如下的优势:

•授权服务不受业务的影响,如果业务网关宕机了,那至少不会影响代理商网关的业务授权系统的使用•授权服务一旦建立,一般就很难进行升级,除非特殊情况。•在敏捷开发中,业务系统可能发布频繁,电商业务系统可能每天都是在频繁升级更新,这样也不至于影响了授权系统服务导致代理商业务受到影响

代理商业务引入进来后,同时又增加了秒杀活动,发现成交量大大增大,支付订单集中在某一时刻翻了十几倍,这时候整个电商业务API网关已经扛不住了,负载了几台可能也有点吃力;开发人员经过跟架构师一起讨论,得出了扛不住的原因:主要是秒杀活动高并发的支付,以至于整个电商业务系统受到影响,故准备把支付系统从业务系统中拆分出成独立的支付网关,并做了一定的负载,成功解决了以上问题,这时候整个电商系统架构图就演变成如下:

支付网关服务抽离后的优势:

•支付网关服务更新不会太频繁,可以减少整个系统的因为发布导致的一系列问题,增强稳定性•支付系统出现宕机不影响整个电商系统的使用,用户还可以浏览商品等等其他操作,技术和运维人员也比较好排查定位问题所在;提升用户体验,同时提升排查问题的效率。授权中心:单独一个服务网关,访问支付业务网关电商业务网关代理商业务网关都需要先通过授权中心获得授权拿到访问令牌AccessToken 才能正常的访问这些网关,这样授权模块就不会受任何的业务影响,同时各个业务网关也不需要写同样的授权业务的代码;业务网关仅仅只需关注本身的业务即可,授权中心仅仅只需要关注维护授权;经过这样升级改造后整个系统维护性得到很大的提高,相关的业务也可以针对具体情况进行选择性的扩容。

上面的电商网关演变架构图中我这里没有画出具体的请求流向,偷了个赖,这里还是先把OAuth2.0 的授权大体的流程图单独贴出来:

由于授权网关服务之前单独抽离出来了,这次把支付业务网关拆分出来就也比较顺利,一下子就完成了电商系统的架构升级。今天这篇文章的目的架构升级也就完成了,想要深入后续电商系统架构升级的同学可以关注后续给大家带来的微服务的相关分享,到时继续以这个例子来进行微服务架构上的演变升级,敬请大家关注。好了下面我们来回归该升级的和核心主题授权中心 IdentityServer4 的应用。

2.2 IdentityServer4 密码授权模式

授权网关服务

静态内存配置方式

定义资源

分资源分为身份资源(Identity resources)和API资源(API resources)。

我们先创建Jlion.NetCore.Identity.Service 网关服务,在网关服务中添加受保护的API资源,创建OAuthMemoryData 类代码如下:

/// <summary>
/// Api资源 静态方式定义
/// </summary>
/// <returns></returns>
public static IEnumerable<ApiResource> GetApiResources()
{return new List<ApiResource>{new ApiResource(OAuthConfig.UserApi.ApiName,OAuthConfig.UserApi.ApiName),};
}

定义客户端Client

OAuthMemoryData 类中定义一个客户端应用程序的Client,我们将使用它来访问我们的API资源代码如下:

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,},};}

AllowedGrantTypes :配置授权类型,可以配置多个授权类型•ClientSecrets:客户端加密方式•AllowedScopes:配置授权范围,这里指定哪些API 受此方式保护•AccessTokenLifetime:配置Token 失效时间•GrantTypes:授权类型,这里使用的是密码模式ResourceOwnerPassword

代码中可以看到有一个OAuthConfig 类,这个类是我单独建的,是用于统一管理,方便维护,代码如下:

 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资源。

接下来OAuthMemoryData 类添加测试用户,代码如下:

/// <summary>
/// 测试的账号和密码
/// </summary>
/// <returns></returns>
public static List<TestUser> GetTestUsers()
{return new List<TestUser>{new TestUser(){SubjectId = "1",Username = "test",Password = "123456"}};
}

上面受保护的资源,和客户端以及测试账号都已经建立好了,现在需要把IdentityServer4 注册到DI中: Startup 中的ConfigureServices 代码如下:

public void ConfigureServices(IServiceCollection services)
{services.AddControllers();#region 内存方式services.AddIdentityServer().AddDeveloperSigningCredential().AddInMemoryApiResources(OAuthMemoryData.GetApiResources()).AddInMemoryClients(OAuthMemoryData.GetClients()).AddTestUsers(OAuthMemoryData.GetTestUsers());#endregion}

代码解读:

AddDeveloperSigningCredential:添加证书加密方式,执行该方法,会先判断tempkey.rsa证书文件是否存在,如果不存在的话,就创建一个新的tempkey.rsa证书文件,如果存在的话,就使用此证书文件。•AddInMemoryApiResources:把受保护的Api资源添加到内存中•AddInMemoryClients :客户端配置添加到内存中•AddTestUsers :测试的用户添加进来

最后通过UseIdentityServer()需要把IdentityServer4 中间件添加到Http管道中,代码如下:

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{if (env.IsDevelopment()){app.UseDeveloperExceptionPage();}app.UseIdentityServer();app.UseRouting();app.UseAuthorization();app.UseEndpoints(endpoints =>{endpoints.MapControllers();});
}

好了,现在授权网关服务代码已经完成,现在直接通过命令行方式启动,命令行启动如下,我指定5000端口,如下图:

电商用户网关Api项目

现在我来新建一个WebApi 大的用户网关服务项目,取名为Jlion.NetCore.Identity.UserApiService,新建后会默认有一个天气预报的api接口,代码如下:

[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();}
}

接下来在Startup 类中添加授权网关服务的配置到DI中,代码如下:

 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的名称相同});}

这里的options.ApiName 需要和网关服务中的Api 资源配置中的ApiName 一致

接下来需要把授权和认证中间件分别注册到Http 管道中,代码如下:

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{if (env.IsDevelopment()){app.UseDeveloperExceptionPage();}app.UseRouting();app.UseAuthentication();app.UseAuthorization();app.UseEndpoints(endpoints =>{endpoints.MapControllers();});
}

现在授权服务网关启用已经完成,只需要在需要保护的Controller 中添加 Authorize 过滤器即可,现在我也通过命令行把需要保护的网关服务启动,如图:

现在我通过postman 工具来单独访问 用户网关服务API,不携带任何信息的情况下,如图:从访问结果可以看出返回401 Unauthorized 未授权。

我们接下来再来访问授权服务网关,如图:

请求网关服务中body中携带了用户名及密码等相关信息,这是返回了access_token 及有效期等相关信息,我们再拿access_token 来继续上面的操作,访问用户业务网关的接口,如图:

访问结果中已经返回了我们所需要的接口数据,大家目前已经对密码模式的使用有了一定的了解,但是这时候可能会有人问我,我生产环境中可能需要通过数据库的方式进行用户信息的判断,以及客户端授权方式需要更加灵活的配置,可通过后台来配置ClientId以及授权方式等,那应该怎么办呢?下面我再来给大家带来生存环境中的实现方式。

数据库匹配验证方式

我们需要通过用户名和密码到数据库中验证方式则需要实现IResourceOwnerPasswordValidator 接口,并实现ValidateAsync 验证方法,简单的代码如下:

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
}

用户密码验证器已经实现完成,现在需要把之前的通过AddTestUsers 方式改成AddResourceOwnerValidator<ResourceOwnerPasswordValidator>() 方式,修改后的代码如下:

public void ConfigureServices(IServiceCollection services)
{services.AddControllers();#region 数据库存储方式services.AddIdentityServer().AddDeveloperSigningCredential().AddInMemoryApiResources(OAuthMemoryData.GetApiResources()).AddInMemoryClients(OAuthMemoryData.GetClients())//.AddTestUsers(OAuthMemoryData.GetTestUsers());.AddResourceOwnerValidator<ResourceOwnerPasswordValidator>();#endregion
}

目前已经实现了用户名和密码数据库验证的方式,但是现在有人会考虑另外一个场景,客户端的授权方式等也需要通过后台可配置的方式,这样比较灵活,不通过代码中静态配置的方式,那应该这么办呢?官方考虑的很周到,我们可以使用IClientStore 接口,同时需要实现FindClientByIdAsync 方法,代码如下:

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;}
}

Startup 中ConfigureServices 代码AddInMemoryClients 改成AddClientStore<> 代码如下:

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 源代码地址:https://github.com/a312586670/IdentityServerDemo

结语:通过IdentityServer4 实现的简单授权中心的架构思想也就完成了,其实这里还缺少一个比较重要的东西,后续再统一补充分享。有错误地方还请留言指出!感谢!!!

如果你觉的不错,请微信扫码关注 【dotNET博士】公众号,后续给您带来更精彩的分享。

扫描二维码

获取更多精彩

长按关注

希望大家帮忙点下右下角星星,写文章、写代码都不容易。鼓励下坚持写文章的动力!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/311214.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

交通标志识别项目教程

项目结构图 下载好项目压缩包后解压&#xff0c;得到以上的文件&#xff0c;首先将画红圈的文件删除&#xff08;如果有&#xff09; 安装软件 安装Anaconda 安装Pycharm 安装格式工厂 在上图中这个位置输入cmd回车&#xff0c;即可打开命令终端。用这样的方式打开命令终端…

内存超频trfc_这只是开始?四款DDR4内存超频效果对比

原标题&#xff1a;这只是开始&#xff1f;四款DDR4内存超频效果对比之前有人说DDR4内存的频率极限是5000MHz&#xff0c;但在最近结束的台湾电脑展上&#xff0c;有些内存的默认频率已经达到了4400MHz&#xff0c;看样子5000MHz的频率极限很快要实现了&#xff0c;由此也说明5…

素数-试除法和埃式筛选法模板

试除法&#xff1a; bool is_prime(int n) {if (n < 1) return false;for (int i 2;i<sqrt(n);i)//这样写更好!if (n % i0) return false;return true; }埃式筛选法&#xff1a; const int N 1e7; int prime[N 1]; bool vis[N 1];int n_prime(int n) {int k 0;mem…

C# 视频监控系统

去过工厂或者仓库的都知道&#xff0c;在工厂或仓库里面&#xff0c;会有很多不同的流水线&#xff0c;大部分的工厂或仓库&#xff0c;都会在不同流水线的不同工位旁边安装一台电脑&#xff0c;一方面便于工位上的师傅把产品的重要信息录入系统&#xff0c;便于公司系统数据采…

sklearn svm如何选择核函数_机器学习之支持向量机多种核模型对比

机器学习xueyifeiyun1989zx&#xff0c;公众号&#xff1a;围着围巾的小黑机器学习之监督学习实战前文我们提到机器学习中的监督学习&#xff0c;其中有一个模型是我们提到的但是没有训练测试的&#xff0c;叫做支持向量机(简称SVM)。支持向量机也是监督学习里面一个非常容易理…

7的序列-数论

题目背景 墨家机关城即将被攻陷&#xff0c;墨家家主无意间发现了一道逃生密道&#xff0c;但这道密道需要密码&#xff0c;机智的你决定参与密码的破译。 题目描述 密码门上有两行数字序列&#xff0c;数字均为非负整数&#xff0c;根据门上的古语&#xff0c;你需要求出满足…

程序员过关斩将--从每秒6000写请求谈起

点击上方“蓝字”关注我们菜菜哥&#xff0c;紧急求助呀怎么回事&#xff1f;产品经理砍你了&#xff1f;没有&#xff0c;只是写了个新项目&#xff0c;上线就被压垮了什么功能&#xff0c;这么强悍&#xff1f;一个记录用户观看视频进度信息的功能那如果用户基数大&#xff0…

批量将PPM格式图片转化为JPG格式

将PPM格式图片转化为JPG格式 做图像识别的时候数据集常常是ppm格式的&#xff0c;虽然不影响建模训练&#xff0c;但是我们电脑往往不支持ppm格式的图像展示。 比如到做交通标志识别的时候用到的BelgiumTS交通数据集或者德国GTSRB数据集 下载后得到都是ppm格式的图像。 格式转…

python数据收集整理教案_数据收集整理教案讲解学习

一、数据收集整理第一课时教学目标初步体验数据收集、整理、描述的过程&#xff0c;会用分类数数的方法将数据整理成简单的统计表&#xff0c;初步认识统计表&#xff0c;能正确填写统计表&#xff0c;能从中获得简单统计的结果。通过对学生身边有趣事例的调查活动&#xff0c;…

hdu1873 看病要排队-优先队列

Problem Description 看病要排队这个是地球人都知道的常识。 不过经过细心的0068的观察&#xff0c;他发现了医院里排队还是有讲究的。0068所去的医院有三个医生&#xff08;汗&#xff0c;这么少&#xff09;同时看病。而看病的人病情有轻重&#xff0c;所以不能根据简单的先来…

Magicodes.IE 2.2里程碑需求和建议征集

简介Magicodes.IE是导入导出通用库&#xff0c;支持Dto导入导出以及动态导出&#xff0c;支持Excel、Word、Pdf、Csv和Html。已加入NCC开源组织。Magicodes.IE 2.0发布Github&#xff1a;https://github.com/dotnetcore/Magicodes.IE码云&#xff08;手动同步&#xff0c;不维护…

启动azkaban报错_解决启动Azkaban报错问题:java.lang.NoSuchMethodError: com.google.comm

问题描述&#xff1a;启动Azkaban报错&#xff1a;java.lang.NoSuchMethodError:com.google.common.collect.ImmutableMap.toImmutableMap解决方法&#xff1a;从报错信息来看&#xff0c;是找不到toImmutableMap这个方法。首先找到类ImmutableMap对应的Jar包为guava&#xff0…

(一)tensorflow笔记:Tensor数据类型

常见的数据类型载体 listnp.arraytf.tensor list: 可以存储不同数据类型&#xff0c;缺点不适合存储较大的数据&#xff0c;如图片 np.array: 解决同类型大数据数据的载体&#xff0c;方便数据运算&#xff0c;缺点是在深度学习之前就设计好的&#xff0c;不支持GPU tf.ten…

吃鸡蛋-优先队列

题目描述 小林养了一只母鸡&#xff0c;一连 n 天&#xff0c;每天都可以生下若干个鸡蛋。在第 i 天&#xff0c;母鸡会生下 eggs[i] 个鸡蛋&#xff0c;这些鸡蛋将会在days[i] 天后&#xff08;也就是说&#xff0c;第 i days[i] 天时&#xff09;腐烂&#xff0c;变得无法食…

交通标志识别教程(二)

项目结构图 下载好项目压缩包后解压&#xff0c;得到以上的文件&#xff0c;首先将画红圈的文件删除&#xff08;如果有&#xff09; 安装软件 解压软件包 安装Anaconda 直接下一步&#xff0c;到了这个页面全部勾选&#xff0c;否则不会添加添加环境变量。 安装Pycharm …

深度长文:Power Automation 帮助企业实现数字化转型

01自动化始于您在Ignite 2019上&#xff0c;我们宣布将Flow更改为Power Automate&#xff0c;并在UI Flow连接器的公开预览中引入了机器人流程自动化&#xff08;RPA&#xff09;。我们对几种激动人心的功能感到兴奋&#xff0c;这些功能将在今年全面上市&#xff0c;并想花一点…

数字电路技术可能出现的简答题_数字电子技术复习题(本科)

1数字电子技术复习题(本科)一、简答题&#xff1a;1、简述组合电路和时序电路各自的特点是什么&#xff1f;答&#xff1a;组合电路的特点&#xff1a;任何时刻电路的稳定输出&#xff0c;仅取决于该时刻各个输入变量的取值&#xff0c;组合电路是由门电路组合而成&#xff0c;…

hdu2544 最短路-Floyd算法

Problem Description 在每年的校赛里&#xff0c;所有进入决赛的同学都会获得一件很漂亮的t-shirt。但是每当我们的工作人员把上百件的衣服从商店运回到赛场的时候&#xff0c;却是非常累的&#xff01;所以现在他们想要寻找最短的从商店到赛场的路线&#xff0c;你可以帮助他们…

Pandas的Timestamp转为datetime 类型

在Pandas中我们在处理时间序列的时候常用的方法有&#xff1a; pd.to_datetime()pd.date_range() pandas生成时间索引 # pd.date_range() index pd.date_range("20210101",periods20) index Out[29]: DatetimeIndex([2021-01-01, 2021-01-02, 2021-01-03, 2021-…

什么样的女生适合学计算机?

我需要在这一行中加一些字数&#xff0c;为什么呢&#xff1f;因为我的字数不够300字&#xff0c;无法声明原创&#xff0c;所以我会在这里加一些字数&#xff0c;它们是白色的&#xff0c;你应该看不到&#xff0c;如果你此刻看到了&#xff0c;那你真的太机智了。300字&#…