Asp.net core IdentityServer4与传统基于角色的权限系统的集成

6802cef972168acba5990b3cdaadddef.png

img

写在前面

因为最近在忙别的,好久没水文了 今天来水一篇;

在学习或者做权限系统技术选型的过程中,经常有朋友有这样的疑问 :

“IdentityServer4的能不能做到与传统基于角色的权限系统集成呢?”

“我的公司有几百个接口,IdentityServer4能不能做到关联用户,给这些用户授予不同的接口的权限呢?”

我的回答是,是的,可以!

同时,我还想补充下,IdentityServer4是给我们的授权流程/需求提供一个新的 标准化的选择,而不是限制你的需求;它是一个基础的框架,你可以根据你的需求自定义成任意你要的样子。

OK,下面开始说说我的实现思路,不一定最优只为抛砖引玉。

开始之前

先准备好两个WebApi 项目,分别有两个接口

Hei.UserApi:6001

GetUsername: https://localhost:6001/api/profile/getusername

GetScore:https://localhost:6001/api/Credit/GetScore //用户信用分要求高,期望管理员才可以调用

Hei.OrderApi:6002

GetOrderNo:https://localhost:6002/api/Order/GetOrderNo

GetAddress:https://localhost:6002/api/Delivery/GetAddress //用户地址敏感,期望管理员才可以调用

实现请看源码

准备好两个角色:

R01 管理员

R02 普通用户

准备好两个用户

Bob: subid=1001,普通用户

Alice: subid=1002,管理员

实际用户有多个角色的,本文为了简化问题,一个用户只允许一种角色

角色对应的权限

管理员:可以调用 Hei.UserApiHei.OrderApi的所有接口;

普通用户:只可以调用 Hei.UserApi->GetUsername,和Hei.OrderApi->GetOrderNo;

实现思路

先来看晓晨大佬画的 access_token 验证交互过程图:

131662cfdc30863efa6e44dadaa8056c.png
img
f2c585187007d965fe119a29275b946f.png
image-20220223112832900

可以看到,Token在首次被服务端验证后,后续的验证都在客户端验证的,本文的重点就在这里,需要判断token有没有权限,重写这部分即可;

开始实现

服务端

1、生成自定义token

1、 IdentityServer4 服务端重写IResourceOwnerPasswordValidatorIProfileService 两个接口生成携带有自定义信息的access_token

public class CustomResourceOwnerPasswordValidator : IResourceOwnerPasswordValidator
{public CustomResourceOwnerPasswordValidator(){}public Task ValidateAsync(ResourceOwnerPasswordValidationContext context){if (!string.IsNullOrEmpty(context.UserName) && !string.IsNullOrEmpty(context.Password)){var loginUser = UserService.Users.First(c => c.Username == context.UserName && c.Password == context.Password);if (loginUser != null){context.Result = new GrantValidationResult(loginUser.SubjectId, OidcConstants.AuthenticationMethods.Password, new Claim[]{new Claim("my_phone","10086")}); //这里增加自定义信息return Task.CompletedTask;}}return Task.CompletedTask;}
}

StartUp.cs 启用

builder.AddResourceOwnerValidator<CustomResourceOwnerPasswordValidator>();
builder.AddProfileService<CustomProfileService>();

2、请求一个token来看看:

100916c1354955810e64c896da669456.png
image-20220223115450490
7727b711ddaf100daa27ca670d457670.png
image-20220223115310375

可以看到我这里token携带有了自定义信息 my_phone,同样的,你可以把角色id直接放这里,或者直接跟用户的subid关联(本demo就是);

客户端

1、自定义授权标签CustomRBACAuthorize

public class CustomRBACAuthorizeAttribute : AuthorizeAttribute{public CustomRBACAuthorizeAttribute(string policyName=""){this.PolicyName = policyName;}public string PolicyName{get{return PolicyName;}set{Policy = $"{Const.PolicyCombineIdentityServer4ExternalRBAC}{value.ToString()}";}}}

后面接口打这个标签就表示使用基于自定义的与权限校验

2、自定义授权 IAuthorizationRequirement

public class CustomRBACRequirement: IAuthorizationRequirement{public string PolicyName { get; }public CustomRBACRequirement(string policyName){this.PolicyName = policyName;}}

3、自定义IAuthorizationPolicyProvider

public class CustomRBACPolicyProvider : IAuthorizationPolicyProvider{private readonly IConfiguration _configuration;public DefaultAuthorizationPolicyProvider FallbackPolicyProvider { get; }public CustomRBACPolicyProvider(IConfiguration configuration, IOptions<AuthorizationOptions> options){_configuration = configuration;FallbackPolicyProvider = new DefaultAuthorizationPolicyProvider(options);}public Task<AuthorizationPolicy> GetDefaultPolicyAsync(){return FallbackPolicyProvider.GetDefaultPolicyAsync();}public Task<AuthorizationPolicy> GetFallbackPolicyAsync(){return Task.FromResult<AuthorizationPolicy>(null);}public Task<AuthorizationPolicy> GetPolicyAsync(string policyName){if (policyName.StartsWith(Const.PolicyCombineIdentityServer4ExternalRBAC, StringComparison.OrdinalIgnoreCase)){var policys = new AuthorizationPolicyBuilder();//这里使用自定义Requirementpolicys.AddRequirements(new CustomRBACRequirement(policyName.Replace(Const.PolicyCombineIdentityServer4ExternalRBAC,"")));return Task.FromResult(policys.Build());}return Task.FromResult<AuthorizationPolicy>(null);}}

4、自定义Requirement的的 AuthorizationHandler

/// <summary>
/// 处理CustomRBACRequirement的逻辑
/// </summary>
/// <param name="context"></param>
/// <param name="requirement"></param>
/// <returns></returns>
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, CustomRBACRequirement requirement)
{var subid = context.User?.FindFirst(ClaimTypes.NameIdentifier)?.Value;var routeData = _httpContextAccessor.HttpContext?.GetRouteData();var curentAction = routeData?.Values["action"]?.ToString();var curentController = routeData?.Values["controller"]?.ToString();//入口程序集,用来标识某个apivar apiName = Assembly.GetEntryAssembly().GetName().Name;if (string.IsNullOrWhiteSpace(subid) == false && string.IsNullOrWhiteSpace(curentAction) == false && string.IsNullOrWhiteSpace(curentController) == false){//核心就在这里了,查出用户subid对应的角色权限,然后做处理判断有没有当前接口的权限//我这里是demo就简单的模拟下,真实的权限数据应该都是写数据库或接口的var userPermission = PermissionService.GetUserPermissionBySubid(apiName, subid);if (userPermission != null && userPermission.Authorised.ContainsKey(curentController)){                    var authActions = userPermission.Authorised[curentController];//这里判断当前用户的角色有当前action/controllers的权限//(真实的权限划分由你自己定义,比如你划分了只读接口,只写接口、特殊权限接口、内部接口等,在管理后台上分组,打标签/标记然后授予角色就行)if (authActions?.Any(action => action == curentAction) == true){context.Succeed(requirement);}}}return Task.CompletedTask;
}

jwt 的token本来是去中心化的,现在这样一来,每次请求进来都去调接口验证可以说是违背了去中心化的思想,所以保证性能问题得自己解决;

权限数据

public class PermissionService
{/// <summary>/// 权限信息(实际上这些应该存在数据库)/// </summary>public static List<PermissionEntity> Permissions = new List<PermissionEntity>{//RoleId R01 是管理员,有两个Api的多个接口的权限new PermissionEntity{ PermissionId="0001",RoleId="R01", ApiName="Hei.UserApi",Authorised=new Dictionary<string, List<string>>{{ "Profile",new List<string>{ "GetUsername"}},{ "Credit",new List<string>{ "GetScore"}},}},new PermissionEntity{ PermissionId="0002",RoleId="R01", ApiName="Hei.OrderApi",Authorised=new Dictionary<string, List<string>>{{ "Delivery",new List<string>{ "GetAddress"}},{ "Order",new List<string>{ "GetOrderNo"}},}},//RoleId R02 是普通员工,有两个Api的多个 部分 接口的权限new PermissionEntity{ PermissionId="0001",RoleId="R02", ApiName="Hei.UserApi",Authorised=new Dictionary<string, List<string>>{{ "Profile",new List<string>{ "GetUsername"}},//{ "Credit",new List<string>{ "GetScore"}}, //用户信用分接口权限就不给普通员工了}},new PermissionEntity{ PermissionId="0002",RoleId="R02", ApiName="Hei.OrderApi",Authorised=new Dictionary<string, List<string>>{//{ "Delivery",new List<string>{ "GetAddress"}}, //用户地址信息也是{ "Order",new List<string>{ "GetOrderNo"}},}}};

当然这些数据一般都是根据你的权限需求存数据库的,与你的权限管理后台相配合;

5、注册自定义授权处理程序

/// <summary>/// 提交自定义角色的授权策略/// </summary>/// <param name="services"></param>/// <returns></returns>public static IServiceCollection AddCustomRBACAuthorizationPolicy(this IServiceCollection services){services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();services.AddSingleton<IAuthorizationPolicyProvider, CustomRBACPolicyProvider>();services.AddSingleton<IAuthorizationHandler, CustomRBACRequirementHandler>();return services;}

6、在接口上使用自定义授权标签CustomRBACAuthorize

[Route("api/[controller]/[action]")][ApiController]public class CreditController : ControllerBase{/// <summary>/// 获取信用分/// </summary>/// <param name="id"></param>/// <returns></returns>[HttpGet][CustomRBACAuthorize] //这里就表名public int GetScore(string id){return 666;}}

7、测试结果

管理员1001 角色id R01  Alice

f4b62b8474aae04dc238530f03cfecab.png
image-20220223151041196

请求:

90d22447d60e05d0c931b8d7e745cb6f.png
image-20220223152252049

可以看到都是 200

普通用户1002 角色id R02  Bob

88d64fa8f17a65d37d2403a1a5a57815.png
image-20220223151144846

请求:

a138b045ee29d6e45e2506bdb839282a.png
image-20220223152233656

可以看到获取用户信用积分、订单投递地址的接口403了,与我们全面的设定相符;

总结

就是一个简单的思路

1、给access_token 带上自定义信息;

2、在客户端重写本地验证/权限校验逻辑即可;

其实token黑白名单,token撤销原理类似 希望能帮上一点小忙;

IdentityServer4就是一个工具,希望大家不要给它设定太多的限制“不能做这个,不能做那个等等”

源码

https://github.com/gebiWangshushu/cnblogs-demos/tree/dev/IdentityServerWithRBAC.Example

如果能有个小星星那就再好不过了(✧◡✧)

参考

https://docs.microsoft.com/zh-cn/aspnet/core/security/authorization/iauthorizationpolicyprovider?view=aspnetcore-3.1#multiple-authorization-policy-providers

https://www.cnblogs.com/stulzq/p/9226059.html

153ae5c50c48253805a30c334ca76094.png

文章博客园地址请点击“阅读原文”

不给我点个赞再走吗~

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

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

相关文章

算法小白——基本排序算法入门

计算的 时间复杂度&#xff08;最差、平均、和最好性能&#xff09;&#xff0c;依据列表&#xff08;list&#xff09;的大小(n)。一般而言&#xff0c;好的性能是O(n log n)&#xff0c;且坏的性能是O(n^2)。对于一个排序理想的性能是O(n)。仅使用一个抽象关键比较运算的排序…

使用Dynamic LINQ创建高级查询服务

前言在以前的文章中&#xff0c;我们介绍了使用AutoFilterer.Generators创建高级查询服务。但是&#xff0c;AutoFilterer.Generators只能提供简单的范围筛选&#xff1a;今天&#xff0c;我们介绍如何使用Dynamic LINQ轻松实现更强大的高级查询服务。Demo创建ASP.NET Core Web…

线程池的开源实现(mariadb和percona版本)

2019独角兽企业重金招聘Python工程师标准>>> 一、"Thread pool in MariaDB 5.5" 线程池解决的问题&#xff1a; 传统mysql使用一个线程处理一个客户端连接&#xff0c;如果许多的并发用户&#xff0c;将使性能下降。因为大量的线程将引起上下文交换&#…

C++之goto

1 goto code: result:

ABP vNext微服务架构详细教程——结束语

ABP vNext微服务架构详细教程——简介ABP vNext微服务架构详细教程——架构介绍ABP vNext微服务架构详细教程——身份管理服务ABP vNext微服务架构详细教程——基础服务层ABP vNext微服务架构详细教程——聚合服务ABP vNext微服务架构详细教程——身份认证服务ABP vNext微服务架…

创建 linux分区命令,Linux中创建分区

在很多情况下我们可能需要在使用Linux的时候创建新的分区来帮助我们更好的学习和工作&#xff0c;接下来我就详细的介绍一下如何利用fdisk这条命令进行硬盘分区。Linux磁盘分区和windows存在区别&#xff0c;Linux分区分为三类 主分区 扩展分区 和逻辑分区。然而它们三个关系有…

Android studio之Error:(23, 17) Failed to resolve: junit:junit:4.12

1 probleam 2 resolve method // testCompile junit:junit:4.12

20杨氏矩阵查找

问题描述&#xff1a;在一个m行n列二维数组中&#xff0c;每一行都按照从左到右递增的顺序排序&#xff0c;每一列都按照从上到下递增的顺序排序。请完成一个函数&#xff0c;输入这样的一个二维数组和一个整数&#xff0c;判断数组中是否含有该整数。 例如下面的二维数组就…

玩一玩微信公众号开发(一) 接入系统

一开始我准备学一学微信开发。后来看了一下文档&#xff0c;需要自己搭服务器进行接口对调。有点麻烦所以就没继续学下去。现在学习过了Kotlin、Spring Boot很多东西&#xff0c;感觉可以比较方便的进行开发了。今天就来回过头学习一下。 准备工作 申请公众号 首先说明一下&…

Android studio之NDK integration is deprecated in the current plugin解决办法

1 、问题 编译Android项目&#xff0c;出现下面错误 NDK integration is deprecated in the current plugin 2、解决办法 在我们 gradle.properties(Project Properties) file add android.useDeprecatedNdktrue

使用 System.Net.Http.Json 简化 HttpClient 的使用

使用 System.Net.Http.Json 简化 HttpClient 的使用Intro从 .NET Core 3.1 开始&#xff0c;微软添加了一个 System.Net.Http.Json 的扩展&#xff0c;可以用来简化 HttpClient 的使用&#xff0c;看到在很多项目里还并未开始使用&#xff0c;所以想向大家介绍一下SamplePostAs…

MySQL的四种不同查询的分析

1.前置条件&#xff1a;本次是基于小数据量&#xff0c;且数据块在一个页中的最理想情况进行分析&#xff0c;可能无具体的实际意义&#xff0c;但是可以借鉴到各种复杂条件下&#xff0c;因为原理是相同的,知小见大&#xff0c;见微知著&#xff01;打开语句分析并确认是否已经…

.NET6之MiniAPI(十八):OpenAPI swagger

从本篇开始&#xff0c;介绍一些很不错的三方库&#xff0c;来丰富MiniAPI的使用。在创建MiniAPI项目时&#xff0c;模板提供了一个是否启用OpenAPI的选项&#xff0c;足见这个三方库的优势和强大。OpenAPI为我们测试API提供了强大的支持&#xff0c;调用API的开发人员&#xf…

Android之ndk-build出现c:28:51: error: ‘get_string‘ undeclared here (not in a function)解决办法

1 problem ndk-build error: get_string undeclared here (not in a function) 2 resolve jstring get_strstring(JNIEnv* env, jobject thiz) {return (*env)->NewStringUTF(env, "I am chenyu, 动态注册JNI"); }jint add_int(JNIEnv* env, jobject jobj, ji…

BeetleX服务网关授权配置

很多应用服务都需要授权访问&#xff0c;为了更好地统一处理这种授权验证&#xff0c;服务网关提供了JWT验证插件用于解决这问题。网关的JWT验证插件默认是关闭的&#xff0c;只有当开启后网关才会对请求进行拦截验证处理。开启开启JWT验证需要在插件管理里开启Webapi JWT验证插…

Linux下的压缩与解压缩

tar命令zip命令unzip命令gzip命令bzip2命令tar命令&#xff1a;压缩和解压缩tar格式的文件格式&#xff1a;tar [主选项辅选项] 文件或目录【主选项告诉tar要做什么事&#xff0c;是必须要有的&#xff0c;辅选项是辅助使用的&#xff0c;和通常的选项一样&#xff0c;可选】主…

Android之JNI动态注册native方法和JNI数据简单使用

1、爆结果照片 2、介绍JNI注册方式 JVM 查找 native 方法有两种方式: 1)、按照 JNI 规范的命名规则(静态注册) 2) 、调用 JNI 提供的 RegisterNatives 函数,将本地函数注册到 JVM 中(动态注册) 静态注册的实现可以参考我的这篇博客 http://blog.csdn.net/u01…

Linux怎么查询全部容器时间,linuxea:如何单单修改docker容器的系统时间

一般情况下&#xff0c;我们仅仅需要修改容器的时间与我们宿主机的实际实际一致即可&#xff0c;我们知道&#xff0c;默认情况下docker容器是不允许访问系统时钟&#xff0c;但是有一款开源的软件使这样的需求变成了可能。此lib拦截用于检索当前时间和日期的所有系统调用&…

关闭系统索引(转)

转自“http://jingyan.baidu.com/article/d621e8daeaaa392865913f0a.html” 关闭系统索引 Windows索引服务是为文件、电子邮件和其他内容提供内容索引、属性缓存和搜索结果。 Windows系统在安装完成后&#xff0c;会逐步创建特定文件、文件夹和其它目标的索引&#xff0c;例如开…

如何编译 dotnet/aspnetcore 源代码

前言最近&#xff0c;准备为 dotnet/aspnetcore 修改 issue&#xff0c;但是在 clone 代码后&#xff0c;发现要编译成功&#xff0c;远没有想象中那么容易。因此&#xff0c;将整个过程进行记录&#xff0c;以供大家参考。以下操作都是在 Windows 10 下完成。0.环境准备详见官…