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;即可打开命令终端。用这样的方式打开命令终端…

C# 视频监控系统

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

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

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

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

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

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

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

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;不维护…

交通标志识别教程(二)

项目结构图 下载好项目压缩包后解压&#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;并想花一点…

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

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

ora-00923数据类型不一致_小白学 Python(2):基础数据类型(上)

如果我的文章对您有帮助&#xff0c;请关注支持下作者的公众号&#xff1a;极客挖掘机&#xff0c;获取最新干货推送&#xff1a;)人生苦短&#xff0c;我选Python引言前文传送门小白学 Python(1)&#xff1a;开篇接触一门新的语言&#xff0c;肯定要先了解它的基础数据类型。啥…

如何将项目上传到github详细完整版

今天介绍如何利用pycharm创建一个新的项目&#xff0c;然后将项目上传到github&#xff0c;以便日后的学习记录&#xff0c;和版本管理。比如现在我想创建一个项目专门用来学习和研究时间序列算法。 创建虚拟环境 # 创建一个新的虚拟环境 conda create -n TimeSeries python3…

[Abp vNext微服务实践] - 搭建租户管理服务

一、简介ABP模板项目中已经提供了租户登录和管理功能&#xff0c;但是模板项目是单体应用结构&#xff0c;无法单独部署租户服务&#xff0c;所以难以满足微服务的需求。本篇文章将会介绍搭建ABP租户管理服务&#xff0c;并单独部署应用。二、创建工程2.1 创建TenantService.Ho…

编写高性能的C#代码(三)使用SPAN

原文来自互联网&#xff0c;由长沙DotNET技术社区编译。如译文侵犯您的署名权或版权&#xff0c;请联系小编&#xff0c;小编将在24小时内删除。作者介绍&#xff1a;史蒂夫戈登&#xff08;Steve Gordon&#xff09;是Microsoft MVP&#xff0c;Pluralsight的作者&#xff0c;…

pycharm配置git拉取项目代码,并添加版本控制

安装Git 打开网页进入git官网&#xff0c;找到git官网下载地址&#xff0c;下载git工具并且安装。 pycharm配置git 点击File -> Settings -> Version Control -> Git 选择Git安装的路径&#xff0c;点击OK 选择一个项目 进入我们需要拉取的项目&#xff0c;点击…

.NET Core开发实战(第22课:异常处理中间件:区分真异常与逻辑异常)--学习笔记(上)...

22 | 异常处理中间件&#xff1a;区分真异常与逻辑异常这一节我们来讲解一下错误处理的最佳实践系统里面异常处理&#xff0c;ASP.NET Core 提供了四种方式1、异常处理页2、异常处理匿名委托方法3、IExceptionFilter4、ExceptionFilterAttribute源码链接&#xff1a;https://gi…

MYSQL开窗函数详解

基本概念 MYSQL8.0支持窗口函数&#xff08;Window Function&#xff09;&#xff0c;也称分析函数。窗口函数与组分聚合函数类似&#xff0c;但是每一行数据都会生成一个结果。如果我们将mysql与pandas中的DataFrame做类比学习的话他们的对应关系如下&#xff1a; SQL分组聚…

你可能需要了解一下的中台

【中台学习】| 作者 / Edison Zhou这是恰童鞋骚年的第201篇原创文章在数字化转型热潮下&#xff0c;各家企业都想建设中台&#xff0c;那么中台是怎么发展起来的&#xff1f;有哪些类型的中台&#xff1f;中台到底是个啥&#xff1f;本文为你一一解答这些问题。1学习背景与前言…

github运行不流畅问题

快速流畅访问Github工具 下载链接如下&#xff1a; 链接&#xff1a;https://pan.baidu.com/s/1RwdrNK__Vx-AEuUr8sM6pg 提取码&#xff1a;a0tw –来自百度网盘超级会员V3的分享 下载后解压&#xff0c;双击运行.exe文件 运行后长这样&#xff0c;给它丢一边不管就行了。

聊聊微信的Dark模式

大家好&#xff0c;我是Z哥。这周微信公布了一个我期待已久的好消息。周一的时候对外公布说&#xff0c;已经完成了iOS版本的Dark模式开发&#xff0c;可能会在下一个版本上线。▲截图来源于微博&#xff0c;版权归原作者所有真的是千呼万唤使出来&#xff0c;很多人期待这个功…

chrome浏览器快速访问stackoverflow

原因&#xff1a;国内网非常多的网站都使用免费的 Google CDN 服务来加载某些 js、字体样式库以提升网页浏览体验&#xff0c;例如 jQuery、Google Fonts。但是目前 Google 的大多数网站在大陆无法正常访问&#xff0c;因此这些本身是加快网页载入的库反而成为了阻塞网站加载的…