• .NET 云原生架构师训练营(权限系统 代码实现 ActionAccess)--学习笔记


    目录

    • 开发任务
    • 代码实现

    开发任务

    • DotNetNB.Security.Core:定义 core,models,Istore;实现 default memory store
    • DotNetNB.Security.ActionAccess:扫描 action;添加 action authorize filter;添加集成方式

    代码实现

    对于一个 web 项目,Filter 是在构建构建 builder 的时候添加的

    builder.Services.AddControllers(options =>
    {
        options.Filters.Add<>()
    })
    

    这里不可能让用户手动添加,所以需要有一个扩展方法给用户调用

    using Microsoft.AspNetCore.Mvc;
    
    namespace DotNetNB.Security.ActionAccess
    {
        public static class MvcOptionsExtensions
        {
            public static MvcOptions AddActionAccessControl(this MvcOptions options)
            {
                options.Filters.Add();
                return options;
            }
        }
    }
    

    创建 Resource,包含 Key 和 Data 两个属性

    namespace DotNetNB.Security.Core.Models
    {
        public class Resource
        {
            public string Key { get; set; }
    
            public object Data { get; set; }
        }
    }
    

    创建 ActionResource,继承 Resource,包含 ControllerName,ActionName,RouteTemplate 和 HttpVerb 几个属性

    using DotNetNB.Security.Core.Models;
    
    namespace DotNetNB.Security.ActionAccess
    {
        public class ActionResource : Resource
        {
    
        }
    
        public class ActionResourceData
        {
            public string? ControllerName { get; set; }
    
            public string? ActionName { get; set; }
            
            public  string DisplayName { get; set; }
    
            public string? RouteTemplate { get; set; }
    
            public string? HttpVerb { get; set; }
        }
    }
    

    定义一个 IResourceManager 接口,提供创建资源的方法

    using DotNetNB.Security.Core.Models;
    
    namespace DotNetNB.Security.Core
    {
        public interface IResourceManager
        {
            public Task CreateAsync(Resource resource);
    
            public Task CreateAsync(IEnumerable resources);
        }
    }
    

    参考 ASP .NET Core 源码中的 ActionEndpointDataSourceBase:

    https://github.com/dotnet/aspnetcore/blob/main/src/Mvc/Mvc.Core/src/Routing/ActionEndpointDataSourceBase.cs

    创建 endpoint 的时候有一个 action 的列表 ActionDescriptors

    var endpoints = CreateEndpoints(_actions.ActionDescriptors.Items, Conventions);
    

    它的类型是一个 IActionDescriptorCollectionProvider,专门用于扫描获取所有的 action

    private readonly IActionDescriptorCollectionProvider _actions;
    

    在 ActionAccess 模块的 ActionResourceProvider 中把它加进来

    using Microsoft.AspNetCore.Mvc.Infrastructure;
    
    namespace DotNetNB.Security.ActionAccess
    {
        public class ActionResourceProvider
        {
            private readonly IActionDescriptorCollectionProvider _actionDescriptorCollectionProvider;
    
            public ActionResourceProvider(IActionDescriptorCollectionProvider actionDescriptorCollectionProvider)
            {
                _actionDescriptorCollectionProvider = actionDescriptorCollectionProvider;
            }
        }
    }
    

    在 host 启动的时候扫描获取所有的 action 信息,定义一个 IResourceProvider 接口

    namespace DotNetNB.Security.Core
    {
        public interface IResourceProvider
        {
            public Task> ExecuteAsync();
        }
    }
    

    ActionResourceProvider 实现这个接口,从 ActionDescriptors 获取 action 信息,构建 ActionResourceData

    public async Task> ExecuteAsync()
    {
        var actions = _actionDescriptorCollectionProvider.ActionDescriptors.Items;
        var actionResources = new List();
    
        foreach (var action in actions)
        {
            if (action is ControllerActionDescriptor)
            {
                var actionDescriptor = action as ControllerActionDescriptor;
                var httpMethod = action.EndpointMetadata.Where(m => m is HttpMethodMetadata).FirstOrDefault() as HttpMethodMetadata;
    
                var routeAttribute =
                    actionDescriptor?.EndpointMetadata.FirstOrDefault(m => m is RouteAttribute) as RouteAttribute;
    
                var resourceData = new ActionResourceData();
                resourceData.HttpVerb = httpMethod?.HttpMethods.First();
                resourceData.ActionName = actionDescriptor?.ActionName;
                resourceData.ControllerName = actionDescriptor?.ControllerName;
                resourceData.RouteTemplate = routeAttribute?.Template;
                resourceData.DisplayName = action.DisplayName;
    
                actionResources.Add(new ActionResource()
                {
                    Data = resourceData,
                    Key = actionDescriptor.GetSecurityKey()
                });
            }
        }
    
        return await Task.FromResult(actionResources);
    }
    

    参考 ASP .NET Core 源码中的 AuthorizeFilter:

    https://github.com/dotnet/aspnetcore/blob/main/src/Mvc/Mvc.Core/src/Authorization/AuthorizeFilter.cs

    AuthorizeFilter 中有一个 OnAuthorizationAsync 的方法,首先获取 policy 执行器,然后执行了认证,接着执行授权,根据授权结果修改 AuthorizationFilterContext,我们权限主要是 Forbidden 的结果,返回 403 即可

    public virtual async Task OnAuthorizationAsync(AuthorizationFilterContext context)
    {
        if (context == null)
        {
            throw new ArgumentNullException(nameof(context));
        }
    
        if (!context.IsEffectivePolicy(this))
        {
            return;
        }
    
        // IMPORTANT: Changes to authorization logic should be mirrored in security's AuthorizationMiddleware
        var effectivePolicy = await GetEffectivePolicyAsync(context);
        if (effectivePolicy == null)
        {
            return;
        }
    
        var policyEvaluator = context.HttpContext.RequestServices.GetRequiredService();
    
        var authenticateResult = await policyEvaluator.AuthenticateAsync(effectivePolicy, context.HttpContext);
    
        // Allow Anonymous skips all authorization
        if (HasAllowAnonymous(context))
        {
            return;
        }
    
        var authorizeResult = await policyEvaluator.AuthorizeAsync(effectivePolicy, authenticateResult, context.HttpContext, context);
    
        if (authorizeResult.Challenged)
        {
            context.Result = new ChallengeResult(effectivePolicy.AuthenticationSchemes.ToArray());
        }
        else if (authorizeResult.Forbidden)
        {
            context.Result = new ForbidResult(effectivePolicy.AuthenticationSchemes.ToArray());
        }
    }
    

    DynamicAuthorizeFilter 继承自 AuthorizeFilter,只获取 ControllerActionDescriptor 类型的 action,如果 permissions 中不包含 actionKey 则返回 403

    using Microsoft.AspNetCore.Mvc;
    using Microsoft.AspNetCore.Mvc.Authorization;
    using Microsoft.AspNetCore.Mvc.Controllers;
    using Microsoft.AspNetCore.Mvc.Filters;
    
    namespace DotNetNB.Security.ActionAccess
    {
        public class DynamicAuthorizeFilter : AuthorizeFilter
        {
            public override async Task OnAuthorizationAsync(AuthorizationFilterContext context)
            {
                var actionDescriptor = context.ActionDescriptor as ControllerActionDescriptor;
                if (actionDescriptor == null)
                {
                    return;
                }
    
                base.OnAuthorizationAsync(context);
                if (context.Result != null)
                {
                    return;
                }
    
                var permissions = context.HttpContext.User.Claims.Where(c => c.Type == Core.ClaimsTypes.Permission);
                var actionKey = actionDescriptor.GetSecurityKey();
    
                var values = permissions.Select(p => p.Value);
                if (!values.Contains(actionKey))
                {
                    context.Result = new ForbidResult();
                }
            }
        }
    }
    

    在 ClaimsTypes 中增加一个 Permission 类型的 ClaimsType

    namespace DotNetNB.Security.Core
    {
        public static class ClaimsTypes
        {
            public const string Permission = "Permission";
        }
    }
    

    为了避免重复代码,添加一个 GetSecurityKey 的扩展方法

    using Microsoft.AspNetCore.Mvc.Controllers;
    using Microsoft.AspNetCore.Mvc.Internal;
    
    namespace DotNetNB.Security.ActionAccess;
    
    public static string GetSecurityKey(this ControllerActionDescriptor descriptor)
    {
        var httpMethod = descriptor.EndpointMetadata.FirstOrDefault(m => m is HttpMethodMetadata) as HttpMethodMetadata;
    
        return string.Format($"{descriptor?.ControllerName}-{descriptor?.ActionName}-{httpMethod.HttpMethods.First()}");
    }
    

    这样就完成了 DotNetNB.Security.ActionAccess 模块的 ActionResourceProvider 和 DynamicAuthorizeFilter

    GitHub源码链接:

    https://github.com/MingsonZheng/dotnetnb.security

    课程链接

    https://appsqsyiqlk5791.h5.xiaoeknow.com/v1/course/video/v_5f39bdb8e4b01187873136cf?type=2

    知识共享许可协议

    本作品采用知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议进行许可。

    欢迎转载、使用、重新发布,但务必保留文章署名 郑子铭 (包含链接: http://www.cnblogs.com/MingsonZheng/ ),不得用于商业目的,基于本文修改后的作品务必以相同的许可发布。

    如有任何疑问,请与我联系 (MingsonZheng@outlook.com) 。

  • 相关阅读:
    知乎回答推广怎么做?知乎回答如何做到置顶?
    map和set底层实现【C++】
    三层架构图解
    1011 循环神经网络 RNN
    DSP_TMS320F28335_优秀的串口通信框架
    文科生学Python:我卸载又安装过三次Anaconda | 观察
    springboot+高校学生实习档案管理 毕业设计-附源码221508
    【HarmonyOS学习】动画
    VPS、独服和云服务器哪种最适合您的业务?
    会话跟踪技术概述 [JavaWeb][Servlet]
  • 原文地址:https://www.cnblogs.com/MingsonZheng/p/15898449.html