使用.NET从零实现基于用户角色的访问权限控制

使用.NET从零实现基于用户角色的访问权限控制

本文将介绍如何实现一个基于.NET RBAC 权限管理系统,如果您不想了解原理,可查看推送的另一篇文章关于Sang.AspNetCore.RoleBasedAuthorization[1] 库是使用介绍,直接使用该库即可。

背景

在设计系统时,我们必然要考虑系统使用的用户,不同的用户拥有不同的权限。主流的权限管理系统都是RBAC模型(Role-Based Access Control 基于角色的访问控制)的变形和运用,只是根据不同的业务和设计方案,呈现不同的显示效果。

在微软文档中我们了解了《基于角色的授权》[2],但是这种方式在代码设计之初,就设计好了系统角色有什么,每个角色都可以访问哪些资源。针对简单的或者说变动不大的系统来说这些完全是够用的,但是失去了灵活性。因为我们不能自由的创建新的角色,为其重新指定一个新的权限范围,毕竟就算为用户赋予多个角色,也会出现重叠或者多余的部分。

RBAC(Role-Based Access Control)即:基于角色的权限控制。通过角色关联用户,角色关联权限的方式间接赋予用户权限。

ece103324217a92d16189bddefd4fe33.png

RBAC模型可以分为:RBAC0、RBAC1、RBAC2、RBAC3 四种。其中RBAC0是基础,也是最简单的,今天我们就先从基础的开始。

资源描述的管理

在开始权限验证设计之前我们需要先对系统可访问的资源进行标识和管理。在后面的权限分配时,我们通过标识好的资源进行资源和操作权限的分配。

资源描述

创建一个 ResourceAttribute 继承 AuthorizeAttribute 和 IAuthorizationRequirement 资源描述属性,描述访问的角色需要的资源要求。通过转化为 Policy 来对 策略的授权[3] 提出要求。

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true, Inherited = true)]
public class ResourceAttribute: AuthorizeAttribute, IAuthorizationRequirement
{private string _resouceName;private string? _action;/// <summary>/// 设置资源类型/// </summary>/// <param name="name">资源名称</param>/// <exception cref="ArgumentNullException">资源名称不能为空</exception>public ResourceAttribute(string name){if (string.IsNullOrEmpty(name)){throw new ArgumentNullException(nameof(name));}string[] resourceValues = name.Split('-');_resouceName = resourceValues[0];if (resourceValues.Length > 1){Action = resourceValues[1];}else{Policy = resourceValues[0];}}/// <summary>/// 获取资源名称/// </summary>/// <returns></returns>public string GetResource(){return _resouceName;}/// <summary>/// 获取操作名称/// </summary>public string? Action{get{return _action;}set{_action = value;if (!string.IsNullOrEmpty(value)){//把资源名称跟操作名称组装成PolicyPolicy = _resouceName + "-" + value;}}}
}

获得所有资源

我们标识好系统中的资源后,还需要获取到我们最终程序中都标识有哪些资源,这里就需使用 ASP.NET Core 中的应用程序模型[4]。可以在程序启动时获取到所有的 Controller 和 Controller 中的每一个方法,然后通过查询 ResourceAttribute 将其统一存储到静态类中。

创建一个 ResourceInfoModelProvider 继承 IApplicationModelProvider,其执行顺序我们设置为=> -989。其执行顺序:

•首先 (Order=-1000):DefaultApplicationModelProvider•然后(Order= -990):AuthorizationApplicationModelProvider CorsApplicationModelProvider•接着是这个 ResourceInfoModelProvider

其核心代码如下:

/// <summary>
/// 基于其 Order 属性以倒序调用
/// </summary>
/// <param name="context"></param>
public void OnProvidersExecuted(ApplicationModelProviderContext context)
{if (context == null){throw new ArgumentNullException(nameof(context));}//获取所有的控制器List<ResourceAttribute> attributeData = new List<ResourceAttribute>();foreach (var controllerModel in context.Result.Controllers){//得到ResourceAttribute//Controller 的特性var resourceData = controllerModel.Attributes.OfType<ResourceAttribute>().ToArray();if (resourceData.Length > 0){attributeData.AddRange(resourceData);}//Controller 中的每个方法的特性foreach (var actionModel in controllerModel.Actions){var actionResourceData = actionModel.Attributes.OfType<ResourceAttribute>().ToArray();if (actionResourceData.Length > 0){attributeData.AddRange(actionResourceData);}}}// 整理信息集中存入全局foreach (var item in attributeData){ResourceData.AddResource(item.GetResource(), item.Action);}
}

授权控制的实现

接下来我们要对授权控制来进行编码实现,包含自定义授权策略的实现和自定义授权处理程序。

动态添加自定义授权策略

关于自定义授权策略提供程序[5]的说明,这里不再赘述微软的文档,里面已经介绍了很详细,这里我们通过其特性可以动态的创建自定义授权策略,在访问资源时我们获取到刚刚标识的 Policy 没有处理策略,就直接新建一个,并传递这个策略的权限检查信息,当然这只是一方面,更多妙用,阅读文档里面其适用范围的说明即可。

/// <summary>
/// 自定义授权策略
/// 自动增加 Policy 授权策略
/// </summary>
/// <param name="policyName">授权名称</param>
/// <returns></returns>
public Task<AuthorizationPolicy> GetPolicyAsync(string policyName)
{// 检查这个授权策略有没有AuthorizationPolicy? policy = _options.GetPolicy(policyName);if (policy is null){_options.AddPolicy(policyName, builder =>{builder.AddRequirements(new ResourceAttribute(policyName));});}return Task.FromResult(_options.GetPolicy(policyName));
}

授权处理程序

前面我们已经可以动态创建授权的策略,那么关于授权策略的处理[6]我们可以实现 AuthorizationHandler 根据传递的策略处理要求对本次请求进行权限的分析。

internal class ResourceAuthorizationHandler : AuthorizationHandler<ResourceAttribute>
{/// <summary>/// 授权处理/// </summary>/// <param name="context">请求上下文</param>/// <param name="requirement">资源验证要求</param>/// <returns></returns>protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, ResourceAttribute requirement){// 需要有用户if (context.User is null) return Task.CompletedTask;if (context.User.IsInRole(ResourceRole.Administrator) // 超级管理员权限,拥有 SangRBAC_Administrator 角色不检查权限|| CheckClaims(context.User.Claims, requirement) // 符合 Resource 或 Resource-Action 组合的 Permission){context.Succeed(requirement);}return Task.CompletedTask;}/// <summary>/// 检查 Claims 是否符合要求/// </summary>/// <param name="claims">待检查的claims</param>/// <param name="requirement">检查的依据</param>/// <returns></returns>private bool CheckClaims(IEnumerable<Claim> claims, ResourceAttribute requirement){return claims.Any(c =>string.Equals(c.Type, ResourceClaimTypes.Permission, StringComparison.OrdinalIgnoreCase)&& (string.Equals(c.Value, requirement.GetResource(), StringComparison.Ordinal)|| string.Equals(c.Value, $"{requirement.GetResource()}-{requirement.Action}", StringComparison.Ordinal)));}
}

这里我们提供了一个内置固定角色名的超级管理员用户,其请求不进行权限检查。

最后

这里我们已经实现了简单的 RBAC 权限设计,之后我们主要在生成 JWT 时带上可访问资源的Permission即可。

new Claim(ResourceClaimTypes.Permission,"查询")

当然,如果直接放在 jwt 中会让 Token 变得很长,虽然我其实并不理解微软的 ClaimTypes 使用一个URI标识,如果有了解的朋友可以帮我解个惑,万分感谢 https://stackoverflow.com/questions/72293184/ 。

回到这个问题,我们可以再设计一个中间件,在获取到用户的角色名时将其关于角色权限的ClaimTypes加入到 content.User 即可。关于这一方面的详细介绍和实现可以看下一篇文章。

本文介绍的相关代码已经提供 Nuget 包,并开源了代码,感兴趣的同学可以查阅:https://github.com/sangyuxiaowu/Sang.AspNetCore.RoleBasedAuthorization

如有错漏之处,敬请指正。

References

[1] Sang.AspNetCore.RoleBasedAuthorization: https://www.nuget.org/packages/Sang.AspNetCore.RoleBasedAuthorization
[2] 《基于角色的授权》: https://learn.microsoft.com/zh-cn/aspnet/core/security/authorization/roles?view=aspnetcore-6.0
[3] 策略的授权: https://learn.microsoft.com/zh-cn/aspnet/core/security/authorization/policies?view=aspnetcore-6.0
[4] 使用 ASP.NET Core 中的应用程序模型: https://learn.microsoft.com/zh-cn/aspnet/core/mvc/controllers/application-model?view=aspnetcore-6.0
[5] 自定义授权策略提供程序: https://learn.microsoft.com/zh-cn/aspnet/core/security/authorization/iauthorizationpolicyprovider?view=aspnetcore-6.0
[6] 授权策略的处理: https://learn.microsoft.com/zh-cn/aspnet/core/security/authorization/policies?view=aspnetcore-6.0

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

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

相关文章

数据归一化

数据归一化 数据的标准化是将数据按比例缩放&#xff0c;使之落入一个小的特定区间&#xff0c;一般为0到1之间。在某些比较和评价的指标处理中经常会用到&#xff0c;去除数据的单位限制&#xff0c;将其转化为无量纲的纯数值&#xff0c;便于不同单位或量级的指标能够进行比较…

vi is failed with error E382: Cannot write, 'buftype' option is set in Linux

在linux下生成jar文件遇到了编码问题&#xff0c;于是想vi t.jar&#xff0c;在保存是报错&#xff1a;E382: Cannot write, buftype option is set 解决方法&#xff1a; 可以用下面的命名查看buftype的设置&#xff0c;当buftypenofile时&#xff0c;不能保存文件&#xff0c…

列表生成式的使用

输入&#xff1a;[Hello, World, 18, Apple, None] 输出&#xff1a;[hello, world, apple] L [Hello, World, 18, Apple, None] print([w.lower() for w in L if isinstance(w, str)])# -- coding: utf-8 -- L [Hello, World, 18, Apple, None] L2 [] L2 [w.lower() for w…

matlab 12位 显示不出来,求助大神,为何不同机器运行MATLAB结果不同

求助&#xff1a;不同机器运行MATLAB结果不同我调用MATLAB优化工具箱的库函数fmincon&#xff0c;使用相同的初始解(可行解)&#xff0c;对同一个问题进行局部搜索(算法为序列二次规划&#xff0c;即SQP)&#xff0c;但在不同机器上得到的结果不同。一共有五台机器 (为了方便&a…

.NET性能系列文章一:.NET7的性能改进

这些方法在.NET7 中变得更快照片来自 CHUTTERSNAP[1] 的 Unsplash[2]欢迎阅读.NET 性能系列的第一章。这一系列的特点是对.NET 世界中许多不同的主题进行研究、比较性能。正如标题所说的那样&#xff0c;本章节在于.NET7 中的性能改进。你将看到哪种方法是实现特定功能最快的方…

UVA - 10061 How many zero#39;s and how many digits ?

n!x*b^y, 当x为正整数时,最大的y就是n!末尾0的个数了, 把n,b分别拆成素因子相乘的形式: 比如, n5,b16 n5,b2^4, 非常明显,末尾0的个数为0 10进制时,n!a*10^x b进制时,n!c*b^y 非常明显,n!的位数就是最大的x1 这里计算我用了log,精度设置为1e-9 #include<iostream> #inclu…

丁洪波 -- 不要“ 总是拿着微不足道的成就来骗自己”

都市快报实盘大赛25期&#xff1a;于海飞/丁洪波荣获冠亚军 七禾网 时间&#xff1a;2010-11-02 12:47:05 来源&#xff1a;期货中国10月30日下午&#xff0c;2010年浙商期货实盘大赛第三季度&#xff08;都市快报实盘大赛第25期&#xff09;颁奖典礼在天科大厦浙商期货大会议室…

面试专题(Mysql及Mongodb)

2019独角兽企业重金招聘Python工程师标准>>> mysql面试题 1. 各个数据库存储引擎区别 mysql的存储引擎是针对表进行设置的&#xff0c;一个库的不同表可以设置不同的存储引擎&#xff0c;mysql默认支持多种存储引擎&#xff0c;以适用不同领域的数据库应用需要&…

织梦网站翻页php,dedecms织梦网站列表页和内容页分页样式

织梦分页标签{dede:pagelist istitem"index,pre,next,end,option,info," listsize"5"/}&#xff0c;{dede:prenext getpre/}&#xff0c;{dede:prenext getnext/}。默认样式和使用模板css样式布局不一样,这时又不想重写样式&#xff0c;我们可以修改织梦标…

通过中间件添加用户的Claim

本文主要介绍 Sang.AspNetCore.RoleBasedAuthorization[1] 库如何通过中间件实现对用户 Claim 的添加。背景前面我们介绍了通过对自定义授权策略和自定义授权处理程序的使用实现了基本的RBAC权限设计&#xff0c;将大量的用户可访问资源及操作的标识直接放到用户的 JWT Token 中…

部署也是工程的一部分,也要编程(自动化)

部署和开发一样&#xff0c;同样面临变化。同样有复杂的细节。 同样应该代码化&#xff0c;自动化。把复杂性、思路&#xff0c;操作&#xff0c;都固化下来&#xff0c;显式表达。 不要“雪花”式配置。 把最近看的文章摘抄一下 集句&#xff1a; 1频繁做让你感到痛苦的事情&a…

KDD走进阿里 数百专家聚集探讨产学研一体化

6月29日&#xff0c;由阿里巴巴集团、中国中文信息学会、KDD China联合主办的数据挖掘前沿发展与未来论坛在杭州举行&#xff0c;会议吸引了来自国际顶级高校和知名企业的近300名专家学者到场参会、近30000人在线观看。论坛除了分享最新的数据挖掘领域最新科研成果及研发思路外…

zookeeper学习03 使用场景

zookeeper实际应用场景 zookeeper能够实现哪些场景 1&#xff09;订阅发布/配置中心 watcher机制 统一配置管理&#xff08;disconf&#xff09; 实现配置信息的集中式原理和数据的动态更新 实现配置中心有俩种模式&#xff1a;push,pull 长轮询 zookeeper采用的是推拉相结合的…

php模板引擎循环start,PHP模板引擎Smarty内建函数section,sectionelse用法详解

本文实例讲述了PHP模板引擎Smarty内建函数section,sectionelse用法。分享给大家供大家参考&#xff0c;具体如下&#xff1a;section 是 Smarty 模板中除了 foreach 以外的另一种处理循环的方案&#xff0c;section 比 foreach 要灵活&#xff0c;就像是一个改进的 foreach 语句…

OpenHarmony操作系统与龙芯2K1000LA芯片完成适配,龙架构平台获得开源鸿蒙认证

近日&#xff0c;龙芯中科与软通动力控股公司鸿湖万联共同完成OpenHarmony操作系统与龙芯2K1000LA处理器的适配&#xff0c;“乘风1000”开发板&#xff08;搭载龙芯2K1000LA&#xff09;荣获OpenHarmony生态产品兼容性证书。至此&#xff0c;万物互联的OpenHarmony生态体系再次…

struts2开发action 的三种方法以及通配符、路径匹配原则、常量

struts2开发action 的三种方法 1、继承ActionSupport public class UserAction extends ActionSupport {// Action中业务处理方法public String login() {System.out.println("UserAction.login()"); // return "success";return SUCCESS;} } 2、实现…

闭包--闭包作用之保护(一)

闭包作用:保护 形成私有作用域,保护里面的私有变量不受外界干扰例如多人协作开发&#xff1a;A的代码有fn(),B的代码有fn(),但是他们不相互影响 // A的代码<script>(function() {function fn1() {console.log("aa")}window.fn1 fn1;})()// window.fn1() //11&…

left join 和 inner join

2019独角兽企业重金招聘Python工程师标准>>> left join 和 inner join 首先 MySQL 中 inner join 的效率确实要高于 left join。所以没必要使用 left join 转弯成 inner join 的效果。这样不但效率降低&#xff0c;可读性也会降低。 Number1 select from t1 left j…

oracle 数据库中拆分,oracle数据库字符串拆分

第一种 直接返回切分的字符串create or replace function Get_StrArrayLength(av_str varchar2,--要分割的字符串av_split varchar2 --分隔符号)return numberislv_str varchar2(1000);lv_length number;beginlv_str:ltrim(rtrim(av_str));lv_length:0;while instr(lv_str,av_s…

Vue3+.NET6,轻松开发管理后台!(可复用)

在GitHub是没找到简单好用的Vue3.NET6管理后台项目&#xff0c;有收藏的请评论区分享。这里分享一套Vue3 Axios TS Vite Element Plus .NET 6 WebAPI JWT SqlSugar的通用管理后台&#xff0c;前后端分离架构&#xff0c;各种最新框架组件&#xff0c;实现了管理后台几乎…