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

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

yuyutoo 2024-12-03 19:36 2 浏览 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


相关推荐

TCP协议原理,有这一篇就够了

先亮出这篇文章的思维导图:TCP作为传输层的协议,是一个软件工程师素养的体现,也是面试中经常被问到的知识点。在此,我将TCP核心的一些问题梳理了一下,希望能帮到各位。001.能不能说一说TC...

Win10专业版无线网络老是掉线的问题

有一位电脑基地的用户,使用...

学习计算机网络需要掌握以下几方面基础知识

计算机基础知识操作系统:了解常见操作系统(如Windows、Linux)的基本操作和网络配置,例如如何设置IP地址、子网掩码、网关和DNS服务器等,以及如何通过命令行工具(如ping、tr...

网络工程师的圣经!世界级网工手绘268张图让TCP/IP直接通俗易懂

要把知识通俗地讲明白,真的不容易。——读者说TCP/IP从字面意义上讲,有人可能会认为TCP/IP是指TCP和IP两种协议。实际生活当中有时候也确实就是这两种协议。然而在很多情况下,它只是...

三分钟了解通信知识TCP与IP协议(含“通信技术”资料分享)

TCP/IPTCP/IP分层模型①应用层...

网闸与防火墙:网络安全设备的差异与应用

在网络安全领域,网闸(安全隔离网闸,GAP)和防火墙(Firewall)是两类重要的防护设备。尽管它们都服务于网络安全防护,但在设计理念、技术原理、安全效能及适用场景等方面存在显著差异,以下从五个维度...

S7-300的TCP/IP通信

一、首先在项目中创建2个S7-300的站点;二、硬件组态中,设置合适的TCP/IP地址,在同一网段内;...

西门子S7-1500 PLC的 MODBUS TCP通信

MODBUSTCP使MODBUS_RTU协议运行于以太网,MODBUSTCP使用TCP/IP和以太网在站点间传送MODBUS报文,MODBUSTCP结合了以太网物理网络和网络标准TC...

系统规划与管理师新版备考必备:第7章考点思维导图解析

备考系统规划与管理师的小伙伴们,福利又来啦!今天为大家带来《系统规划与管理师(第2版)》第7章考点的思维导图,助你高效梳理重点,让备考更有方向!...

TCP/IP、Http、Socket 有何区别与联系?

HTTP协议对应于应用层,Socket则是对TCP/IP协议的封装和应用(程序员层面上)。HTTP是应用层协议,主要解决如何包装数据。而我们平时说的最多的Socket是什么呢?实际上...

西门子PLC串口协议与以太网通信协议对比

西门子plc品牌众多,通信协议的类型就更多了,具体可分为串口协议和以太网通信协议两大类。...

网络编程懒人入门(十三):一泡尿的时间,快速搞懂TCP和UDP的区别

本文引用了作者Fundebug的“一文搞懂TCP与UDP的区别”一文的内容,感谢无私分享。1、引言...

程序员必备的学习笔记《TCP/IP详解(一)》

为什么会有TCP/IP协议在世界上各地,各种各样的电脑运行着各自不同的操作系统为大家服务,这些电脑在表达同一种信息的时候所使用的方法是千差万别。就好像圣经中上帝打乱了各地人的口音,让他们无法合作一样...

一文读懂TCP/IP协议工作原理和工作流程

简述本文主要介绍TCP/IP协议工作原理和工作流程。含义TCP/IP协议,英文全称TransmissionControlProtocol/InternetProtocol,包含了一系列构成互联网...

如何在 Windows 10 和 Windows 11 上重置 TCP/IP 堆栈

传输控制协议/Internet协议,通常称为TCP/IP,是您的WindowsPC如何与Internet上的其他设备进行通信的关键部分。但是当事情出错时会发生什么?你如何解决它?幸运的...

取消回复欢迎 发表评论: