目录

  • 开发任务
  • 代码实现

开发任务

  • 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) 。

.NET 云原生架构师训练营(权限系统 代码实现 ActionAccess)--学习笔记的更多相关文章

  1. .NET 云原生架构师训练营(建立系统观)--学习笔记

    目录 目标 ASP .NET Core 什么是系统 什么是系统思维 系统分解 什么是复杂系统 作业 目标 通过整体定义去认识系统 通过分解去简化对系统的认识 ASP .NET Core ASP .NE ...

  2. .NET 云原生架构师训练营(对象过程建模)--学习笔记

    目录 UML OPM OPM优化 UML 1997年发布UML标准 主要域 视图 图 主要概念 结构 静态视图 类图 类.关联.泛化.依赖关系.实现.接口 用例视图 用例图 用例.参与者.关联.扩展. ...

  3. .NET 云原生架构师训练营(设计原则&&设计模式)--学习笔记

    目录 设计原则 设计模式 设计原则 DRY (Don't repeat yourself 不要重复) KISS (Keep it stupid simple 简单到傻子都能看懂) YAGNI (You ...

  4. .NET 云原生架构师训练营(责任链模式)--学习笔记

    目录 责任链模式 源码 责任链模式 职责链上的处理者负责处理请求,客户只需要将请求发送到职责链上即可,无需关心请求的处理细节和请求的传递,所以职责链将请求的发送者和请求的处理者解耦了 何时使用:在处理 ...

  5. .NET 云原生架构师训练营(系统架构)--学习笔记

    目录 对外展现的功能 内部功能 功能交互与价值通路 系统架构 目标 认识系统的价值通路 认识功能架构,通过把功能结构与形式结构结合来描述系统架构 受益原则 好的架构必须使人受益,要想把架构做好,就要专 ...

  6. .NET 云原生架构师训练营(模块一 架构师与云原生)--学习笔记

    目录 什么是软件架构 软件架构的基本思路 单体向分布式演进.云原生.技术中台 1.1 什么是软件架构 1.1.1 什么是架构? Software architecture = {Elements, F ...

  7. .NET 云原生架构师训练营(权限系统 RGCA 架构设计)--学习笔记

    目录 项目核心内容 实战目标 RGCA 四步架构法 项目核心内容 无代码埋点实现对所有 API Action 访问控制管理 对 EF Core 实体新增.删除.字段级读写控制管理 与 Identity ...

  8. .NET 云原生架构师训练营(权限系统 RGCA 开发任务)--学习笔记

    目录 目标 模块拆分 OPM 开发任务 目标 基于上一讲的模块划分做一个任务拆解,根据任务拆解实现功能 模块拆分 模块划分已经完成了边界的划分,边界内外职责清晰 OPM 根据模块拆分画出 OPM(Ob ...

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

    目录 开发任务 代码实现 开发任务 DotNetNB.Security.Core:定义 core,models,Istore:实现 default memory store DotNetNB.Secu ...

随机推荐

  1. JDBC 处理sql查询多个不确定参数

    JDBC程序,为了防止SQL注入,通常需要进行参数化查询,但是如果存在多个不确定参数,就比较麻烦了,查阅了一些资料,最后解决了这个问题,现在这里记录一下:   public List<TabDl ...

  2. ADO.NET数据访问基础与综合应用2020年10月31日20:17:09学习笔记

    四.创建数据表 1.数据表的名称. 2.表中的字段名.数据类型.是否可以为空.字段的约束.必备的字段(通常会有一个ID,表示实体的唯一性:可以直接手写,也可以使用种子标识自动生成,给定起始值,给定增长 ...

  3. HUAWEI网络设备恢复Console口密码

    密码遗忘 本章介绍了Console口丢失的处理方法,建议用户妥善保管密码,并定期修改. 恢复Console口密码 设备提供如下方法恢复Console口密码.•方法一:通过STelnet/Telnet登 ...

  4. c++中构造函数与析构函数

    构造函数与析构函数 构造函数与析构函数1. 构造函数2. 析构函数3. 拷贝函数4. 总结 在c++中有2个特殊的函数:构造函数和析构函数,它们分别对类对象进行初始化和清理工作. 1. 构造函数 构造 ...

  5. C语言字幕从外向中间汇聚

    演示数据中多个字符,从两端向中间移动,向中间汇聚 简单,粗暴,先上代码: Sleep()函数属于<windows.h>头文件中. sizeof()函数求右下标:数组内是数字时,求右下标要- ...

  6. leeetcode 20. 有效的括号

    20. 有效的括号 问题描述 给定一个只包括 '(',')','{','}','[',']' 的字符串,判断字符串是否有效. 有效字符串需满足: 左括号必须用相同类型的右括号闭合. 左括号必须以正确的 ...

  7. 【vps】教你写一个属于自己的随机图API

    [vps]教你写一个自己的随机图API 前言 刚刚开始使用halo博客的时候,我就发现halo博客系统是可以使用随机图当背景的,所以也是使用了网上一些比较火的随机图API. 在上次发现了各种图片API ...

  8. 【刷题-LeetCode】208. Implement Trie (Prefix Tree)

    Implement Trie (Prefix Tree) Implement a trie with insert, search, and startsWith methods. Example: ...

  9. 【分享】让prometheus支持PUSH模式,可以使用remote write协议推送数据

    2021-10-21补充: 我通过修改源码来让prometheus支持remote-write,纯属多此一举: --enable-feature=remote-write-receiver这个命令行参 ...

  10. 第57篇-profile实例

    之前已经介绍过回边计数和ProfileData与Layout,下面举个具体的例子看下MethodData是怎么利用ProfileData等记录详细的运行时信息的.实例如下: package com.t ...