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

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

背景

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

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

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

graph LR
    A[用户] -->| 属于多个 | B(角色) -->| 角色拥有 | C(权限)

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

资源描述的管理

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

资源描述

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

[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))
            {
                //把资源名称跟操作名称组装成Policy
                Policy = _resouceName + "-" + value;
            }
        }
    }
}

获得所有资源

我们标识好系统中的资源后,还需要获取到我们最终程序中都标识有哪些资源,这里就需使用 ASP.NET Core 中的应用程序模型。可以在程序启动时获取到所有的 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);
    }
}

授权控制的实现

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

动态添加自定义授权策略

关于自定义授权策略提供程序的说明,这里不再赘述微软的文档,里面已经介绍了很详细,这里我们通过其特性可以动态的创建自定义授权策略,在访问资源时我们获取到刚刚标识的 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));
}

授权处理程序

前面我们已经可以动态创建授权的策略,那么关于授权策略的处理我们可以实现 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

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

相关文章
|
1月前
|
SQL XML 关系型数据库
入门指南:利用NHibernate简化.NET应用程序的数据访问
【10月更文挑战第13天】NHibernate是一个面向.NET的开源对象关系映射(ORM)工具,它提供了从数据库表到应用程序中的对象之间的映射。通过使用NHibernate,开发者可以专注于业务逻辑和领域模型的设计,而无需直接编写复杂的SQL语句来处理数据持久化问题。NHibernate支持多种数据库,并且具有高度的灵活性和可扩展性。
40 2
|
2月前
|
SQL 存储 关系型数据库
C#一分钟浅谈:使用 ADO.NET 进行数据库访问
【9月更文挑战第3天】在.NET开发中,与数据库交互至关重要。ADO.NET是Microsoft提供的用于访问关系型数据库的类库,包含连接数据库、执行SQL命令等功能。本文从基础入手,介绍如何使用ADO.NET进行数据库访问,并提供示例代码,同时讨论常见问题及其解决方案,如连接字符串错误、SQL注入风险和资源泄露等,帮助开发者更好地利用ADO.NET提升应用的安全性和稳定性。
274 6
|
3月前
|
算法 Java 测试技术
java 访问ingress https报错javax.net.ssl.SSLHandshakeException: Received fatal alert: protocol_version
java 访问ingress https报错javax.net.ssl.SSLHandshakeException: Received fatal alert: protocol_version
|
3月前
|
API
【Azure Key Vault】.NET 代码如何访问中国区的Key Vault中的机密信息(Get/Set Secret)
【Azure Key Vault】.NET 代码如何访问中国区的Key Vault中的机密信息(Get/Set Secret)
|
4月前
|
开发框架 前端开发 JavaScript
基于SqlSugar的数据库访问处理的封装,支持.net FrameWork和.net core的项目调用
基于SqlSugar的数据库访问处理的封装,支持.net FrameWork和.net core的项目调用
|
4月前
|
开发框架 缓存 NoSQL
基于SqlSugar的数据库访问处理的封装,在.net6框架的Web API上开发应用
基于SqlSugar的数据库访问处理的封装,在.net6框架的Web API上开发应用
|
3月前
【Azure 应用服务】App Service 配置 Application Settings 访问Storage Account得到 could not be resolved: '*.file.core.windows.net'的报错。没有解析成对应中国区 Storage Account地址 *.file.core.chinacloudapi.cn
【Azure 应用服务】App Service 配置 Application Settings 访问Storage Account得到 could not be resolved: '*.file.core.windows.net'的报错。没有解析成对应中国区 Storage Account地址 *.file.core.chinacloudapi.cn
|
3月前
|
开发框架 .NET 数据库连接
ASP.NET Core 标识(Identity)框架系列(一):如何使用 ASP.NET Core 标识(Identity)框架创建用户和角色?
ASP.NET Core 标识(Identity)框架系列(一):如何使用 ASP.NET Core 标识(Identity)框架创建用户和角色?
|
3月前
|
开发框架 JavaScript .NET
Vue与ASP.NET Core Web Api设置localhost与本地ip地址皆可访问
Vue与ASP.NET Core Web Api设置localhost与本地ip地址皆可访问
41 0
|
6月前
|
Windows
windows server 2019 安装NET Framework 3.5失败,提示:“安装一个或多个角色、角色服务或功能失败” 解决方案
windows server 2019 安装NET Framework 3.5失败,提示:“安装一个或多个角色、角色服务或功能失败” 解决方案
1045 0