▲ 点击上方“DotNet NB”关注公众号
回复“1”获取开发者路线图
学习分享 丨作者 / 郑 子 铭
这是DotNet NB 公众号的第188篇原创文章
目录
开发任务
代码实现
开发任务
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 AuthorizationMiddlewarevar 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 authorizationif (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
课程链接
.NET云原生架构师训练营讲什么,怎么讲,讲多久
推荐阅读:
《Kubernetes全栈架构师(Kubeadm高可用安装k8s集群)--学习笔记》
《.NET 云原生架构师训练营(模块一 架构师与云原生)--学习笔记》
《.NET Core开发实战(第1课:课程介绍)--学习笔记》
点击下方卡片关注DotNet NB
一起交流学习