写在前面

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

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

“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 验证交互过程图

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

开始实现

服务端

1、生成自定义token

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

  1. public class CustomResourceOwnerPasswordValidator : IResourceOwnerPasswordValidator
  2. {
  3. public CustomResourceOwnerPasswordValidator()
  4. {
  5. }
  6. public Task ValidateAsync(ResourceOwnerPasswordValidationContext context)
  7. {
  8. if (!string.IsNullOrEmpty(context.UserName) && !string.IsNullOrEmpty(context.Password))
  9. {
  10. var loginUser = UserService.Users.First(c => c.Username == context.UserName && c.Password == context.Password);
  11. if (loginUser != null)
  12. {
  13. context.Result = new GrantValidationResult(loginUser.SubjectId, OidcConstants.AuthenticationMethods.Password, new Claim[]{new Claim("my_phone","10086")}); //这里增加自定义信息
  14. return Task.CompletedTask;
  15. }
  16. }
  17. return Task.CompletedTask;
  18. }
  19. }

StartUp.cs 启用

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

2、请求一个token来看看:

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

客户端

1、自定义授权标签CustomRBACAuthorize

  1. public class CustomRBACAuthorizeAttribute : AuthorizeAttribute
  2. {
  3. public CustomRBACAuthorizeAttribute(string policyName="")
  4. {
  5. this.PolicyName = policyName;
  6. }
  7. public string PolicyName
  8. {
  9. get
  10. {
  11. return PolicyName;
  12. }
  13. set
  14. {
  15. Policy = $"{Const.PolicyCombineIdentityServer4ExternalRBAC}{value.ToString()}";
  16. }
  17. }
  18. }

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

2、自定义授权 IAuthorizationRequirement

  1. public class CustomRBACRequirement: IAuthorizationRequirement
  2. {
  3. public string PolicyName { get; }
  4. public CustomRBACRequirement(string policyName)
  5. {
  6. this.PolicyName = policyName;
  7. }
  8. }

3、自定义IAuthorizationPolicyProvider

  1. public class CustomRBACPolicyProvider : IAuthorizationPolicyProvider
  2. {
  3. private readonly IConfiguration _configuration;
  4. public DefaultAuthorizationPolicyProvider FallbackPolicyProvider { get; }
  5. public CustomRBACPolicyProvider(IConfiguration configuration, IOptions<AuthorizationOptions> options)
  6. {
  7. _configuration = configuration;
  8. FallbackPolicyProvider = new DefaultAuthorizationPolicyProvider(options);
  9. }
  10. public Task<AuthorizationPolicy> GetDefaultPolicyAsync()
  11. {
  12. return FallbackPolicyProvider.GetDefaultPolicyAsync();
  13. }
  14. public Task<AuthorizationPolicy> GetFallbackPolicyAsync()
  15. {
  16. return Task.FromResult<AuthorizationPolicy>(null);
  17. }
  18. public Task<AuthorizationPolicy> GetPolicyAsync(string policyName)
  19. {
  20. if (policyName.StartsWith(Const.PolicyCombineIdentityServer4ExternalRBAC, StringComparison.OrdinalIgnoreCase))
  21. {
  22. var policys = new AuthorizationPolicyBuilder();
  23. //这里使用自定义Requirement
  24. policys.AddRequirements(new CustomRBACRequirement(policyName.Replace(Const.PolicyCombineIdentityServer4ExternalRBAC,"")));
  25. return Task.FromResult(policys.Build());
  26. }
  27. return Task.FromResult<AuthorizationPolicy>(null);
  28. }
  29. }

4、自定义Requirement的的 AuthorizationHandler

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

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

权限数据

  1. public class PermissionService
  2. {
  3. /// <summary>
  4. /// 权限信息(实际上这些应该存在数据库)
  5. /// </summary>
  6. public static List<PermissionEntity> Permissions = new List<PermissionEntity>
  7. {
  8. //RoleId R01 是管理员,有两个Api的多个接口的权限
  9. new PermissionEntity{ PermissionId="0001",RoleId="R01", ApiName="Hei.UserApi",Authorised=new Dictionary<string, List<string>>
  10. {
  11. { "Profile",new List<string>{ "GetUsername"}},
  12. { "Credit",new List<string>{ "GetScore"}},
  13. }
  14. },
  15. new PermissionEntity{ PermissionId="0002",RoleId="R01", ApiName="Hei.OrderApi",Authorised=new Dictionary<string, List<string>>
  16. {
  17. { "Delivery",new List<string>{ "GetAddress"}},
  18. { "Order",new List<string>{ "GetOrderNo"}},
  19. }
  20. },
  21. //RoleId R02 是普通员工,有两个Api的多个 部分 接口的权限
  22. new PermissionEntity{ PermissionId="0001",RoleId="R02", ApiName="Hei.UserApi",Authorised=new Dictionary<string, List<string>>
  23. {
  24. { "Profile",new List<string>{ "GetUsername"}},
  25. //{ "Credit",new List<string>{ "GetScore"}}, //用户信用分接口权限就不给普通员工了
  26. }
  27. },
  28. new PermissionEntity{ PermissionId="0002",RoleId="R02", ApiName="Hei.OrderApi",Authorised=new Dictionary<string, List<string>>
  29. {
  30. //{ "Delivery",new List<string>{ "GetAddress"}}, //用户地址信息也是
  31. { "Order",new List<string>{ "GetOrderNo"}},
  32. }
  33. }
  34. };

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

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

  1. /// <summary>
  2. /// 提交自定义角色的授权策略
  3. /// </summary>
  4. /// <param name="services"></param>
  5. /// <returns></returns>
  6. public static IServiceCollection AddCustomRBACAuthorizationPolicy(this IServiceCollection services)
  7. {
  8. services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
  9. services.AddSingleton<IAuthorizationPolicyProvider, CustomRBACPolicyProvider>();
  10. services.AddSingleton<IAuthorizationHandler, CustomRBACRequirementHandler>();
  11. return services;
  12. }

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

  1. [Route("api/[controller]/[action]")]
  2. [ApiController]
  3. public class CreditController : ControllerBase
  4. {
  5. /// <summary>
  6. /// 获取信用分
  7. /// </summary>
  8. /// <param name="id"></param>
  9. /// <returns></returns>
  10. [HttpGet]
  11. [CustomRBACAuthorize] //这里就表名
  12. public int GetScore(string id)
  13. {
  14. return 666;
  15. }
  16. }

7、测试结果

管理员1001 角色id R01 Alice

请求:

可以看到都是 200

普通用户1002 角色id R02 Bob

请求:

可以看到获取用户信用积分、订单投递地址的接口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

Asp.net core IdentityServer4与传统基于角色的权限系统的集成的更多相关文章

  1. ASP.NET Core 2.1中基于角色的授权

    ASP.NET Core 2.1中基于角色的授权 授权是来描述用户能够做什么的过程.例如,只允许管理员用户可以在电脑上进行软件的安装以及卸载.而非管理员用户只能使用软件而不能进行软件的安装以及卸载.它 ...

  2. C 实现基于角色的权限系统

    本文demo下载地址:http://www.wisdomdd.cn/Wisdom/resource/articleDetail.htm?resourceId=1068 实例使用C# 实现基于角色的权限 ...

  3. ASP.NET Identity 身份验证和基于角色的授权

    ASP.NET Identity 身份验证和基于角色的授权 阅读目录 探索身份验证与授权 使用ASP.NET Identity 身份验证 使用角色进行授权 初始化数据,Seeding 数据库 小结 在 ...

  4. ASP.NET MVC 基于角色的权限控制系统的示例教程

    上一次在 .NET MVC 用户权限管理示例教程中讲解了ASP.NET MVC 通过AuthorizeAttribute类的OnAuthorization方法讲解了粗粒度控制权限的方法,接下来讲解基于 ...

  5. ASP.net MVC 基于角色的权限控制系统的实现

    一.引言 我们都知道ASP.net mvc权限控制都是实现AuthorizeAttribute类的OnAuthorization方法. 下面是最常见的实现方式: public class Custom ...

  6. RBAC: K8s基于角色的权限控制

    文章目录 RBAC: K8s基于角色的权限控制 ServiceAccount.Role.RoleBinding Step 1:创建一个ServiceAccount,指定namespace Step 2 ...

  7. 10.spring-boot基于角色的权限管理页面实现

    10.spring-boot基于角色的权限管理页面实现

  8. webapi框架搭建-安全机制(四)-可配置的基于角色的权限控制

    webapi框架搭建系列博客 在上一篇的webapi框架搭建-安全机制(三)-简单的基于角色的权限控制,某个角色拥有哪些接口的权限是用硬编码的方式写在接口上的,如RBAuthorize(Roles = ...

  9. webapi框架搭建-安全机制(三)-简单的基于角色的权限控制

    webapi框架搭建系列博客 上一篇已经完成了“身份验证”,如果只是想简单的实现基于角色的权限管理,我们基本上不用写代码,微软已经提供了authorize特性,直接用就行. Authorize特性的使 ...

随机推荐

  1. 一文看懂B端产品和C端产品

    大纲 什么是B端产品 什么是C端产品 为什么会产生B端产品和C端产品 怎么判断一个产品是B端还是C端 B端产品和C端产品存在哪些差异 C端产品经理如何向B端产品经理转型 写在最后   什么是B, Bu ...

  2. Android一句话 | View事件分发

    View中,无论是down,move,还是up,事件都是这样传递的:由dispatchTouchEvent到onTouch,再到onTouchEvent,click是在onTouchEvent中的. ...

  3. 基于华为云服务器的FTP站点搭建

    前言 主要介绍了华为云上如何使用弹性云服务器的Linux实例使用vsftpd软件搭建FTP站点.vsftpd全称是"very secure FTP daemon",是一款在Linu ...

  4. Java高效开发-远程debug

    1.前言 "这怎么回事?在本地还好好,放到服务器就不行了.这该怎么排查,日志也看不出来啥呀",日常开发中经常会出现这种问题,这时候就可以尝试idea远程debug的模式试试 2.使 ...

  5. 搭服务器之kvm--vnc连接虚拟机连接闪退直接消失 以及virsh shutdown命令无效解决办法。

    之前暑期见识到了虚拟化在企业中的应用,感慨不小,以前只是自己在玩儿桌面vmware workstation,安装的虚拟机也没啥大感觉.在公司机房里大家用的dell poweredge 420,8gme ...

  6. Servlet-请求转发

    Servlet-请求转发 请求转发:服务器收到请求后,从一个资源跳转到另一个资源的操作叫请求转发 请求转发特点: 1,浏览器地址栏没有变化 2,他们是一次请求 3,他们共享Request域中的数据 4 ...

  7. 前端基础之javaScript(基本类型-布尔值数组-if-while)

    目录 一:javaScript基本数据类型 1.字符串类型常用方法 2.返回长度 3.移出空白 4.移除左边的空白 5.移出右边的空格 6.返回第n个字符 7.子序列位置 8.根据索引获取子序列 9. ...

  8. Java方法内联

    一.概念 方法内联就是把调用方函数代码"复制"到调用方函数中,减少因函数调用开销的技术   函数调用过程 1.首先会有个执行栈,存储它们的局部变量.方法名.动态连接 2.当一个方法 ...

  9. c++ constexpr用法

    测试环境:windows10 + gcc8.1 1.constexpr产生背景 c++11以后,为了保证写出的代码比以往任何时候的执行效率都要好而进行了许多改善.其中,这种改善之一就是生成常量表达式, ...

  10. What Goes Up Must Come Down

    跳转链接 题目描述 给定一个序列, 求出将此序列变换为单调递增.单调递减 或者先增后减 样例1 输入 7 3 1 4 1 5 9 2 输出 3 样例2 输入 9 10 4 6 3 15 9 1 1 1 ...