百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 编程网 > 正文

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

yuyutoo 2024-12-03 19:36 1 浏览 0 评论

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

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

背景

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

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

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

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

资源描述的管理

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

资源描述

创建一个 ResourceAttribute 继承 AuthorizeAttributeIAuthorizationRequirement 资源描述属性,描述访问的角色需要的资源要求。通过转化为 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="ArgumentException">资源名称不能为空</exception> public ResourceAttribute(string name) { if (string.IsOrEmpty(name)) { throw new ArgumentException(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.IsOrEmpty(value)) { //把资源名称跟操作名称组装成Policy Policy = _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 == ) { throw new ArgumentException(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 ) { _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 ) 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


相关推荐

史上最全的浏览器兼容性问题和解决方案

微信ID:WEB_wysj(点击关注)◎◎◎◎◎◎◎◎◎一┳═┻︻▄(页底留言开放,欢迎来吐槽)●●●...

平面设计基础知识_平面设计基础知识实验收获与总结
平面设计基础知识_平面设计基础知识实验收获与总结

CSS构造颜色,背景与图像1.使用span更好的控制文本中局部区域的文本:文本;2.使用display属性提供区块转变:display:inline(是内联的...

2025-02-21 16:01 yuyutoo

写作排版简单三步就行-工具篇_作文排版模板

和我们工作中日常word排版内部交流不同,这篇教程介绍的写作排版主要是用于“微信公众号、头条号”网络展示。写作展现的是我的思考,排版是让写作在网格上更好地展现。在写作上花费时间是有累积复利优势的,在排...

写一个2048的游戏_2048小游戏功能实现

1.创建HTML文件1.打开一个文本编辑器,例如Notepad++、SublimeText、VisualStudioCode等。2.将以下HTML代码复制并粘贴到文本编辑器中:html...

今天你穿“短袖”了吗?青岛最高23℃!接下来几天气温更刺激……

  最近的天气暖和得让很多小伙伴们喊“热”!!!  昨天的气温到底升得有多高呢?你家有没有榜上有名?...

CSS不规则卡片,纯CSS制作优惠券样式,CSS实现锯齿样式

之前也有写过CSS优惠券样式《CSS3径向渐变实现优惠券波浪造型》,这次再来温习一遍,并且将更为详细的讲解,从布局到具体样式说明,最后定义CSS变量,自定义主题颜色。布局...

柠檬科技肖勃飞:大数据风控助力信用社会建设

...

你的自我界限够强大吗?_你的自我界限够强大吗英文

我的结果:A、该设立新的界限...

行内元素与块级元素,以及区别_行内元素和块级元素有什么区别?

行内元素与块级元素首先,CSS规范规定,每个元素都有display属性,确定该元素的类型,每个元素都有默认的display值,分别为块级(block)、行内(inline)。块级元素:(以下列举比较常...

让“成都速度”跑得潇潇洒洒,地上地下共享轨交繁华
让“成都速度”跑得潇潇洒洒,地上地下共享轨交繁华

去年的两会期间,习近平总书记在参加人大会议四川代表团审议时,对治蜀兴川提出了明确要求,指明了前行方向,并带来了“祝四川人民的生活越来越安逸”的美好祝福。又是一年...

2025-02-21 16:00 yuyutoo

今年国家综合性消防救援队伍计划招录消防员15000名

记者24日从应急管理部获悉,国家综合性消防救援队伍2023年消防员招录工作已正式启动。今年共计划招录消防员15000名,其中高校应届毕业生5000名、退役士兵5000名、社会青年5000名。本次招录的...

一起盘点最新 Chrome v133 的5大主流特性 ?

1.CSS的高级attr()方法CSSattr()函数是CSSLevel5中用于检索DOM元素的属性值并将其用于CSS属性值,类似于var()函数替换自定义属性值的方式。...

竞走团体世锦赛5月太仓举行 世界冠军杨家玉担任形象大使

style="text-align:center;"data-mce-style="text-align:...

学物理能做什么?_学物理能做什么 卢昌海

作者:曹则贤中国科学院物理研究所原标题:《物理学:ASourceofPowerforMan》在2006年中央电视台《对话》栏目的某期节目中,主持人问过我一个的问题:“学物理的人,如果日后不...

你不知道的关于这只眯眼兔的6个小秘密
你不知道的关于这只眯眼兔的6个小秘密

在你们忙着给熊本君做表情包的时候,要知道,最先在网络上引起轰动的可是这只脸上只有两条缝的兔子——兔斯基。今年,它更是迎来了自己的10岁生日。①关于德艺双馨“老艺...

2025-02-21 16:00 yuyutoo

取消回复欢迎 发表评论: