Asp.Net Core 中IdentityServer4 实战之角色授权详解

一、前言

前几篇文章分享了IdentityServer4密码模式的基本授权及自定义授权等方式,最近由于改造一个网关服务,也用到了IdentityServer4的授权,改造过程中发现比较适合基于Role角色的授权,通过不同的角色来限制用户访问不同的Api资源,这里我就来分享IdentityServer4基于角色的授权详解。

IdentityServer4 历史文章目录

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

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

  • Asp.Net Core 中IdentityServer4 授权中心之自定义授权模式

  • Asp.Net Core 中IdentityServer4 授权原理及刷新Token的应用

  • Asp.Net Core 中IdentityServer4 实战之 Claim详解

没有看过之前的几篇文章,我建议先回过头看看上面那几篇文章再来看本篇文章,不过对于大牛来说就可以跳过了。。。。

二、模拟场景

还是按照我的文章风格套路,实战之前先来模拟下应用场景,无场景的实战都是耍流氓,模拟场景更能让大家投入,同时也是自我学习、思考、总结的结晶之处!!!

对于角色授权大家也不陌生,大家比较熟悉的应该是RBAC的设计,这里就不阐述RBAC,有兴趣的可以百度。我们这里简单模拟下角色场景 假如有这么一个数据网关服务服务(下面我统称为数据网关),客户端有三种账号角色(普通用户、管理员用户、超级管理员用户),数据网关针对这三种角色用户分配不同的数据访问权限,场景图如下:

那么这种场景我们会怎么去设计呢?这个场景还算比较简单,角色比较单一,比较固定,对于这种场景很多人可能会考虑到通过Filter过滤器等方式来实现,这当然可以。不过针对这种场景IdentityServer4中本身就支持角色授权,下面我来给大家分享IdentityServer4的角色授权.

三、角色授权实战

授权流程

撸代码之前我们先整理下IdentityServer4的 角色授权流程图,我简单概括画了下,流程图如下:

场景图概括如下:

  • 客户端分为三种核心角色(普通用户、管理员用户、超级管理-老板)用户,三种用户访问同一个数据网关(API资源)

  • 数据网关(API资源)对这三种用户角色做了访问限制。

角色授权流程解释如下:

  • 第一步:不同的用户携带用户密码等信息访问授权中心(ids4)尝试授权

  • 第二步:授权中心对用户授权通过返回access_token给用户同时声明用户的RoleClaim中。。

  • 第三步:客户端携带拿到的access_token尝试请求数据网关(API资源)。

  • 第四步:数据网关收到客户端的第一次请求会到授权中心请求获得验证公钥。

  • 第五步:授权中心返回验证公钥数据网关并且缓存起来,后面不再到授权中心再次获得验证公钥(只会请求一次,除非重启服务)。

  • 第六步:数据网关(ids4)通过验证网关验证access_token是否验证通过,并且验证请求的客户端用户声明的Role是否和请求的API资源约定的的角色一致。如果一致则通过第步返回给用户端,否则直接拒绝请求.

撸代码

代码继续上面几篇文章的例子的续集,你懂的,就不从零开始撸代码啦(强烈建议没看过上面几篇的先看下上面的目录中的几篇,要不然会一头雾水,大佬跳过) 要使IdentityServer4实现的授权中心支持角色验证的支持,我们需要在定义的API资源中添加角色的引入,代码如下:上几篇文章的授权中心(Jlion.NetCore.Identity.Service)的 代码如下:

 /// <summary>/// 资源/// </summary>/// <returns></returns>public static IEnumerable<ApiResource> GetApiResources(){return new List<ApiResource>{new ApiResource(OAuthConfig.UserApi.ApiName,OAuthConfig.UserApi.ApiName),};}

加入角色的支持代码改造如下:

 /// <summary>/// 资源/// </summary>/// <returns></returns>public static IEnumerable<ApiResource> GetApiResources(){return new List<ApiResource>{new ApiResource(OAuthConfig.UserApi.ApiName,OAuthConfig.UserApi.ApiName,new List<string>(){JwtClaimTypes.Role }),};}

API资源中添加了角色验证的支持后,需要在用户登录授权成功后声明Claim用户的Role信息,代码如下:改造前代码:

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}"),new Claim(EnumUserClaim.DisplayName.ToString(),"测试用户"),new Claim(EnumUserClaim.UserId.ToString(),"10001"),new Claim(EnumUserClaim.MerchantId.ToString(),"000100001"),};}#endregion}

为了保留之前文章的源代码,好让之前的文章源代码可追溯,我这里不在源代码上改造升级,我直接新增一个用户密码验证器类, 命名为RoleTestResourceOwnerPasswordValidator,代码改造如下:

 /// <summary>/// 角色授权用户名密码验证器demo/// </summary>public class RoleTestResourceOwnerPasswordValidator : IResourceOwnerPasswordValidator{public async Task ValidateAsync(ResourceOwnerPasswordValidationContext context){try{var userName = context.UserName;var password = context.Password;//验证用户,这么可以到数据库里面验证用户名和密码是否正确var claimList = await ValidateUserByRoleAsync(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>/// 验证用户(角色Demo 专用方法)/// 这里和之前区分,主要是为了保留和博客同步源代码/// </summary>/// <param name="loginName"></param>/// <param name="password"></param>/// <returns></returns>private async Task<List<Claim>> ValidateUserByRoleAsync(string loginName, string password){//TODO 这里可以通过用户名和密码到数据库中去验证是否存在,// 以及角色相关信息,我这里还是使用内存中已经存在的用户和密码var user = OAuthMemoryData.GetUserByUserName(loginName);if (user == null)throw new Exception("登录失败,用户名和密码不正确");//下面的Claim 声明我为了演示,硬编码了,//实际生产环境需要通过读取数据库的信息并且来声明return new List<Claim>(){new Claim(ClaimTypes.Name, $"{user.UserName}"),new Claim(EnumUserClaim.DisplayName.ToString(),user.DisplayName),new Claim(EnumUserClaim.UserId.ToString(),user.UserId.ToString()),new Claim(EnumUserClaim.MerchantId.ToString(),user.MerchantId.ToString()),new Claim(JwtClaimTypes.Role.ToString(),user.Role.ToString())};}#endregion
}

为了方便演示,我直接把Role定义成了一个公共枚举EnumUserRole,代码如下:

/// <summary>
/// 角色枚举
/// </summary>
public enum EnumUserRole
{Normal,Manage,SupperManage
}

GetUserByUserName中硬编码创建了三个角色的用户,代码如下:

 /// <summary>/// 为了演示,硬编码了,/// 这个方法可以通过DDD设计到底层数据库去查询数据库/// </summary>/// <param name="userName"></param>/// <returns></returns>public static UserModel GetUserByUserName(string userName){var normalUser = new UserModel(){DisplayName = "张三",MerchantId = 10001,Password = "123456",Role = Enums.EnumUserRole.Normal,SubjectId = "1",UserId = 20001,UserName = "testNormal"};var manageUser = new UserModel(){DisplayName = "李四",MerchantId = 10001,Password = "123456",Role = Enums.EnumUserRole.Manage,SubjectId = "1",UserId = 20001,UserName = "testManage"};var supperManageUser = new UserModel(){DisplayName = "dotNET博士",MerchantId = 10001,Password = "123456",Role = Enums.EnumUserRole.SupperManage,SubjectId = "1",UserId = 20001,UserName = "testSupperManage"};var list = new List<UserModel>() {normalUser,manageUser,supperManageUser};return list?.Where(item => item.UserName.Equals(userName))?.FirstOrDefault();}

好了,现在用户授权通过后声明的Role也已经完成了,我上面使用的是JwtClaimTypes 默认支持的Role,你也可以不使用JwtClaimTypes类,可以自定义类来实现。最后为了让新关注我的博客用户没看过之前几篇文章的用户不至于一头雾水,我把注册ids中间件代码还是贴出来, 注册新的用户名密码验证器到DI中 代码如下:

 public void ConfigureServices(IServiceCollection services){services.AddControllers();#region 数据库存储方式services.AddIdentityServer().AddDeveloperSigningCredential().AddInMemoryApiResources(OAuthMemoryData.GetApiResources())//.AddInMemoryClients(OAuthMemoryData.GetClients()).AddClientStore<ClientStore>()//.AddResourceOwnerValidator<ResourceOwnerPasswordValidator>().AddResourceOwnerValidator<RoleTestResourceOwnerPasswordValidator>().AddExtensionGrantValidator<WeiXinOpenGrantValidator>().AddProfileService<UserProfileService>();//添加微信端自定义方式的验证#endregion}public void Configure(IApplicationBuilder app, IWebHostEnvironment env){if (env.IsDevelopment()){app.UseDeveloperExceptionPage();}//使用IdentityServer4 的中间件app.UseIdentityServer();app.UseRouting();app.UseAuthorization();app.UseEndpoints(endpoints =>{endpoints.MapControllers();});
}

授权中心的角色支持代码撸完了,我们来改造上几篇文章中说到的用户网关服务,这里我就叫数据网关, 项目:Jlion.NetCore.Identity.UserApiService上一篇关于Asp.Net Core 中IdentityServer4 实战之 Claim详解文章中在数据网关服务中新增了UserController控制器,并添加了一个访问用户基本的Claim信息接口,之前的代码如下:

[ApiController]
[Route("[controller]")]
public class UserController : ControllerBase
{private readonly ILogger<UserController> _logger;public UserController(ILogger<UserController> logger){_logger = logger;}[Authorize][HttpGet]public async Task<object> Get(){var userId = User.UserId();return new{name = User.Name(),userId = userId,displayName = User.DisplayName(),merchantId = User.MerchantId(),};}
}

上面的代码中Authorize没有指定Role,那相当于所有的用户都可以访问这个接口,接下来,我们在UserController中创建一个只能是超级管理员角色才能访问的接口,代码如下

 [Authorize(Roles =nameof(EnumUserRole.SupperManage))][HttpGet("{id}")]public async Task<object> Get(int id){var userId = User.UserId();return new{name = User.Name(),userId = userId,displayName = User.DisplayName(),merchantId = User.MerchantId(),roleName=User.Role()//获得当前登录用户的角色};}

到这里数据网关代码也已经改造完了,我们接下来就是运行结果看看是否正确。

运行

我们分别通过命令行运行我们的授权网关服务和数据网关服务,分别如下图:授权网关还是指定5000 端口,如下图:数据网关跟之前几篇文章一样指定 5001 端口,如下图:

现在授权网关数据网关都已经完美运行起来了,接下来我们通过postman模拟请求。先来通过普通用户(testNormal)请求授权中心获得access_token,如下图:请求验证通过, 再来通过获取到的access_token 获取普通接口:也完美获取到数据 再来访问下标注了supperManage超级管理员的角色接口,如下图:结果跟预想的一样,返回了403访问被拒绝,其他账号运行也是一样,我这里就不一一去运行访问测试了,有兴趣的同学可以到github 上拉起我的源代码进行运行测试, 到这里基于ids4角色授权基础应用也完成了。

结束语:上面分享学习了IdentityServer4 进行角色授权的实战例子,但是从上面的例子中可以发现Controller或者Action中指定Role的使用场景不是很广泛,对于固定的那种角色场景比较适用,但是对于一个庞大的系统来说,用户的权限、角色和API资源是后台灵活可以分配的,这种场景感觉就不是很合适,那IdentityServer4 有没有什么好的方式实现呢?留给大家思考,思考就是思维的一大进步。

博客系列源代码地址:https://github.com/a312586670/NetCoreDemo

感谢语:三月份即将过去,三月份同时也是美好的开始,我的博客从三月份开始整理分享,传承着以一起学习,共同进步为目标,自我自律,开始分享相关技术。文章持续性同步至我的微信公众号【dotNET博士】,这个月来初见成效,一个月内已经荣获500+以上的粉丝,也感谢大家一直以来对我的关注,你的关注让我更有动力分享更好的原创技术文章。还没有关注微信公众号的,搜索"dotNET博士"关注,或者微信扫下面的二维码进行关注,同时大家也可以积极的分享或点个右下角的推荐,让更多人的关注到我的文章。

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

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

相关文章

linux cpu load 值,理解Linux系统中的load average(图文版)转

一、什么是load average&#xff1f;linux系统中的Load对当前CPU工作量的度量 (WikiPedia: the system load is a measure of the amount of work that a computer system is doing)。也有简单的说是进程队列的长度。Load Average 就是一段时间 (1 分钟、5分钟、15分钟) 内平均…

LeetCode 257二叉树的所有路径-简单

给定一个二叉树&#xff0c;返回所有从根节点到叶子节点的路径。 说明: 叶子节点是指没有子节点的节点。 示例: 输入: 1/ \ 2 3\5输出: [“1->2->5”, “1->3”] 解释: 所有根节点到叶子节点的路径为: 1->2->5, 1->3 代码如下: /*** Definition …

[ASP.NET Core 3.1]浏览器嗅探解决部分浏览器丢失Cookie问

今天的干货长驱直入&#xff0c;直奔主题看了前文的同学们应该都知道&#xff0c;搜狗、360等浏览器在单点登录中反复重定向&#xff0c;最终失败报错。原因在于&#xff0c;非Chrome80浏览器不识别Cookie上的SameSitenone属性值,导致认证Cookie在后续请求中被抛弃。截至2020/3…

c语言 最小值算法,C语言实现基于最大堆和最小堆的堆排序算法示例

堆定义堆实际上是一棵完全二叉树&#xff0c;其任何一非叶节点满足性质&#xff1a;Key[i]<key[2i1]&&Key[i]<key[2i2](小顶堆)或者&#xff1a;Key[i]>Key[2i1]&&key>key[2i2](大顶堆)即任何一非叶节点的关键字不大于或者不小于其左右孩子节点的关…

LeetCode100 相同的树-简单

给你两棵二叉树的根节点 p 和 q &#xff0c;编写一个函数来检验这两棵树是否相同。 如果两个树在结构上相同&#xff0c;并且节点具有相同的值&#xff0c;则认为它们是相同的。 示例 1&#xff1a; 输入&#xff1a;p [1,2,3], q [1,2,3] 输出&#xff1a;true 示例 2&a…

Xamarin.Forms读取并展示Android和iOS通讯录 - TerminalMACS客户端

本文同步更新地址&#xff1a;https://dotnet9.com/11520.htmlhttps://terminalmacs.com/861.html阅读导航&#xff1a;一、功能说明二、代码实现三、源码获取四、参考资料五、后面计划一、功能说明完整思维导图&#xff1a;https://github.com/dotnet9/TerminalMACS/blob/mast…

LeetCode 101对称二叉树-简单

给定一个二叉树&#xff0c;检查它是否是镜像对称的。 例如&#xff0c;二叉树 [1,2,2,3,4,4,3] 是对称的。 1/ \2 2/ \ / \ 3 4 4 3但是下面这个 [1,2,2,null,3,null,3] 则不是镜像对称的: 1/ \2 2\ \3 3代码如下&#xff1a; /*** Definition for a binary tree…

paragon+ntfs+linux,NTFS For Mac 超强兼容性

NTFS For Mac是为解决Windows和Mac OS X不兼容问题而开发的低级别档案系统驱动&#xff0c;提供在Mac OS X下完全读/写访问NTFS档案系统的任何版本。兼容mac OS X所有版本、32/64位内核模式&#xff0c;及其它第三方软件。不仅如此&#xff0c;NTFS For Mac 超强兼容性支持更多…

Asp.Net Core Ocelot Consul 微服务

做一个简单的微服务架构如下图&#xff1a;这个图表示的是一个网关代理Consul的两个服务&#xff0c;consul每个服务注册集群安装 Consul的服务&#xff0c;这里安装单机版的&#xff0c;集群版配置最低要求&#xff08;3个Consul server&#xff09;的需要三台虚拟机&#xff…

LeetCode 104二叉树的最大深度-简单

给定一个二叉树&#xff0c;找出其最大深度。 二叉树的深度为根节点到最远叶子节点的最长路径上的节点数。 说明: 叶子节点是指没有子节点的节点。 示例&#xff1a; 给定二叉树 [3,9,20,null,null,15,7]&#xff0c; 3/ \9 20/ \15 7返回它的最大深度 3 。 代码如下: …

以下c语言程序片段用于估测cpu的cache参数,阿里巴巴笔试题

阿里巴巴笔试题1.平均速度最快的排序算法是______。Shell排序快速排序冒泡排序插入排序2014-03-29 18:36:022.某服务进程的QPS(没秒处理的请求个数)较低&#xff0c;在空闲时间RT(响应时间)比较合理。在压力下CPU占用率20%左右。那么可能存在的问题是______。该进程的某个处理过…

.Neter们,你真的应该了解下EFCore3.x

本期导读&#xff1a;技术文&#xff0c;带你了解关于EntityFrameworkCore3.x的那些事&#xff0c;本文共1493个字&#xff0c;阅读大约需要3分钟。文末福利不要错过哦&#xff01;是的各位.Neter&#xff0c;不用怀疑&#xff0c;使用O/RM的开发者越来越多了&#xff0c;从风起…

LeetCode 111二叉树的最小深度-简单

给定一个二叉树&#xff0c;找出其最小深度。 最小深度是从根节点到最近叶子节点的最短路径上的节点数量。 说明&#xff1a;叶子节点是指没有子节点的节点。 示例 1&#xff1a; 输入&#xff1a;root [3,9,20,null,null,15,7] 输出&#xff1a;2 示例 2&#xff1a; 输…

山东省中职信息技术c语言试题及答案,中职计算机专业C语言测试题

高三计算机专业C语言测试题(1)学号姓名&#xff1a;得分&#xff1a;一、选择题40’1、下列选项是C语言保留字的一项是()A DOB gosubC strutD CHAR2、下列选项中可以作为C语言用户标识符的一组是()A void , define, wordB a3_b3, _123, IFC for, _abc , caseD 2a, do, sizeof3、…

istio回归「单体应用」对我们的启发

大家好&#xff0c;我是Z哥。这次分享给大家的是一篇与技术相关的文章&#xff0c;但是我想表达的核心观点并不仅限于技术范围。我们中国有句古话&#xff0c;分久必合&#xff0c;合久必分。很多事物的发展都逃不开这个规律。如今&#xff0c;这件事也正在分布式、微服务概念大…

LeetCode 110平衡二叉树-简单

给定一个二叉树&#xff0c;判断它是否是高度平衡的二叉树。 本题中&#xff0c;一棵高度平衡二叉树定义为&#xff1a; 一个二叉树每个节点 的左右两个子树的高度差的绝对值不超过 1 。示例 1&#xff1a; 输入&#xff1a;root [3,9,20,null,null,15,7] 输出&#xff1a;t…

c语言中如何使用面向对象编程,如何使用C语言的面向对象

我们都知道&#xff0c;C才是面向对象的语言&#xff0c;但是C语言是否能使用面向对象的功能&#xff1f;(1)继承性typedef struct _parent{int data_parent;}Parent;typedef struct _Child{struct _parent parent;int data_child;}Child;在设计C语言继承性的时候&#xff0c;我…

c语言开发环境 推荐,C语言复习和VC++6.0开发环境推荐.ppt

C语言复习和VC6.0开发环境推荐* * * * * * * * * F10-单步调试 F11-进入函数内部调试 ShiftF11-从函数内部回到调用函数(如main) CtrlF10-运行到光标处. ShiftF5-终止调试过程. * 是一个重要概念&#xff0c;可以有效地表示复杂的数据结构&#xff1b; 能动态分配内存&#xff…

LeetCode 112路径总和-简单

给你二叉树的根节点 root 和一个表示目标和的整数 targetSum &#xff0c;判断该树中是否存在 根节点到叶子节点 的路径&#xff0c;这条路径上所有节点值相加等于目标和 targetSum 。 叶子节点 是指没有子节点的节点。 示例 1&#xff1a; 输入&#xff1a;root [5,4,8,11,…

ASP.NET MVC升级到ASP.NET Core MVC踩坑小结

写在前面ASP.NET Core是微软新推出的支持跨平台、高性能、开源的开发框架&#xff0c;它的优势不必多说&#xff0c;因为已经说得太多了。当然&#xff0c;现在依然有着数量庞大的系统运行于.NET Framework上&#xff0c;由于有大量的Break Changes&#xff0c;很多项目项目团队…