Asp.net core IdentityServer4与传统基于角色的权限系统的集成
写在前面
因为最近在忙别的,好久没水文了 今天来水一篇;
在学习或者做权限系统技术选型的过程中,经常有朋友有这样的疑问 :
“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.UserApi
和Hei.OrderApi
的所有接口;
普通用户:只可以调用 Hei.UserApi
->GetUsername,和Hei.OrderApi
->GetOrderNo;
实现思路
先来看晓晨大佬画的 access_token 验证交互过程图:
可以看到,Token在首次被服务端验证后,后续的验证都在客户端验证的,本文的重点就在这里,需要判断token有没有权限,重写这部分即可;
开始实现
服务端
1、生成自定义token
1、 IdentityServer4 服务端重写IResourceOwnerPasswordValidator
和 IProfileService
两个接口生成携带有自定义信息的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来看看:
可以看到我这里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();
//这里使用自定义Requirement
policys.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();
//入口程序集,用来标识某个api
var 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
请求:
可以看到都是 200
普通用户1002 角色id R02 Bob
请求:
可以看到获取用户信用积分、订单投递地址的接口403了,与我们全面的设定相符;
总结
就是一个简单的思路
1、给access_token 带上自定义信息;
2、在客户端重写本地验证/权限校验逻辑即可;
其实token黑白名单,token撤销原理类似 希望能帮上一点小忙;
IdentityServer4就是一个工具,希望大家不要给它设定太多的限制“不能做这个,不能做那个等等”
源码
https://github.com/gebiWangshushu/cnblogs-demos/tree/dev/IdentityServerWithRBAC.Example
如果能有个小星星那就再好不过了(✧◡✧)
参考
https://www.cnblogs.com/stulzq/p/9226059.html
Asp.net core IdentityServer4与传统基于角色的权限系统的集成的更多相关文章
- ASP.NET Core 2.1中基于角色的授权
ASP.NET Core 2.1中基于角色的授权 授权是来描述用户能够做什么的过程.例如,只允许管理员用户可以在电脑上进行软件的安装以及卸载.而非管理员用户只能使用软件而不能进行软件的安装以及卸载.它 ...
- C 实现基于角色的权限系统
本文demo下载地址:http://www.wisdomdd.cn/Wisdom/resource/articleDetail.htm?resourceId=1068 实例使用C# 实现基于角色的权限 ...
- ASP.NET Identity 身份验证和基于角色的授权
ASP.NET Identity 身份验证和基于角色的授权 阅读目录 探索身份验证与授权 使用ASP.NET Identity 身份验证 使用角色进行授权 初始化数据,Seeding 数据库 小结 在 ...
- ASP.NET MVC 基于角色的权限控制系统的示例教程
上一次在 .NET MVC 用户权限管理示例教程中讲解了ASP.NET MVC 通过AuthorizeAttribute类的OnAuthorization方法讲解了粗粒度控制权限的方法,接下来讲解基于 ...
- ASP.net MVC 基于角色的权限控制系统的实现
一.引言 我们都知道ASP.net mvc权限控制都是实现AuthorizeAttribute类的OnAuthorization方法. 下面是最常见的实现方式: public class Custom ...
- RBAC: K8s基于角色的权限控制
文章目录 RBAC: K8s基于角色的权限控制 ServiceAccount.Role.RoleBinding Step 1:创建一个ServiceAccount,指定namespace Step 2 ...
- 10.spring-boot基于角色的权限管理页面实现
10.spring-boot基于角色的权限管理页面实现
- webapi框架搭建-安全机制(四)-可配置的基于角色的权限控制
webapi框架搭建系列博客 在上一篇的webapi框架搭建-安全机制(三)-简单的基于角色的权限控制,某个角色拥有哪些接口的权限是用硬编码的方式写在接口上的,如RBAuthorize(Roles = ...
- webapi框架搭建-安全机制(三)-简单的基于角色的权限控制
webapi框架搭建系列博客 上一篇已经完成了“身份验证”,如果只是想简单的实现基于角色的权限管理,我们基本上不用写代码,微软已经提供了authorize特性,直接用就行. Authorize特性的使 ...
随机推荐
- SRC(不定期更新)
主域名收集 响应包 Content-Security-Policy-Report-Only
- Api自动生成
如果经常对接api, 可以自己写一个自动化生成代码,提高效率 只抛出一个思路,暂不提供源码 使用json+字符串处理+生成文件 发送一个请求,返回字符串转换为 Newtonsoft.Json.Linq ...
- 【记录一个问题】libtask无法在android下编译通过
源码来自:https://github.com/msteinert/libtask 首先是asm.S无法编译通过. 其次,编译context.c出现这些错误: .//context.c:124:19: ...
- DNS主从同步部署
DNS 主从同步原理 主从同步:主每次修改配置文件需要修改一下序列号,主从同步主要 根据序列号的变化. 从DNS:从可以单独修改,主从不会报错.但从修改后,主端同步给从后 从端修改数据会丢失 主从原理 ...
- 一步一步超级详细的zabbix安装教程
安装说明: 1. 虚拟机上安装两台全新Linux: zabbix-server:192.168.255.128 zabbix-agent :192.168.255.129 2. zabbix-serv ...
- 微服务架构 | 5.4 Sentinel 流控、统计和熔断的源码分析
目录 前言 1. Sentinel 的自动装配 1.2 依赖引入 1.3 SentinelWebAutoConfiguration 配置类 1.4 CommonFilter 过滤器 1.5 小结 2. ...
- 关于Linux安装中NAT模式和桥接模式的区别详解(转载)
1.一般我们在创建一个Linux虚拟机时候,会面临三个网络配置选择: 桥接模式.nat模式.host-only模式(主机模式,这个模式用得少,就不介绍了) 2.NAT模式: 所谓nat模式,就是虚拟系 ...
- 不使用pvc的方式在K8S中部署apisix-gateway
不使用pvc的方式在K8S中部署apisix-gateway 简介 我的apisix使用etcd作为数据存储服务器,官方的使用pvc方式或者docker-compose的方式,对于新手不太友好,本篇是 ...
- 分布式系统及CAP理论
一.集中式系统 在学习分布式之前,先了解一下与之相对应的集中式系统是什么样的. 集中式系统用一句话概括就是:一个主机带多个终端.终端没有数据处理能力,仅负责数据的录入和输出.而运算.存储等全部在主机上 ...
- 一行代码轻松修改 Text Field 和 Text View 的光标颜色 — By 昉
众所周知,Text Field 和 Text View 的光标颜色默认都是系统应用的那种蓝色,如图: 而在实际开发中为了让视觉效果更统一,我们可能会想把那光标的颜色设置成和界面色调一致的颜色.其实在 ...