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

博客 分享
0 290
张三
张三 2022-02-15 23:55:22
悬赏:0 积分 收藏

.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<DynamicAuthorizeFilter>();            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<Resource> 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<IEnumerable<Resource>> ExecuteAsync();    }}

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

public async Task<IEnumerable<Resource>> ExecuteAsync(){    var actions = _actionDescriptorCollectionProvider.ActionDescriptors.Items;    var actionResources = new List<ActionResource>();    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<IPolicyEvaluator>();    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) 。

posted @ 2022-02-15 22:44 MingsonZheng 阅读(0) 评论(0) 编辑 收藏 举报
回帖
    张三

    张三 (王者 段位)

    821 积分 (2)粉丝 (41)源码

     

    温馨提示

    亦奇源码

    最新会员