学习分享 丨作者 / 郑 子 铭
这是DotNet NB 公众号的第186篇原创文章
目录
- 项目核心内容
- 实战目标
- RGCA 四步架构法
项目核心内容
- 无代码埋点实现对所有 API Action 访问控制管理
- 对 EF Core 实体新增、删除、字段级读写控制管理
- 与 Identity 进行融合集成
实战目标
- RGCA 四步架构法的应用
- 加深对 OPM 和 OPD 的运用
- 为毕业设计打下基础
RGCA 四步架构法
- Requirement:从利益相关者获取需求
- Goal:将需求转化为目标(功能意图)
- Concept:将目标扩展为完整概念
- Architecture:将概念扩展为架构
从利益相关者获取需求
- 受益原则
- 痛点
- 利益相关者
- 需求分类、排序、特征
受益原则
好的架构必须使人受益,要想把架构做好,就要专注于功能的涌现,使得系统把它的主要功能通过跨越系统边界的接口对外展示出来
痛点
对于企业内部的管理系统而言基本上都会有一个权限管理系统,产品经理在进行产品设计的时候需要先确定每一个菜单,每一个按钮的 key 是什么,再将 key 与权限进行匹配、绑定
但是产品经理无法确定未来所有需要权限管理的地方,所以需要开发人员提前为所有地方设置 key
由于需要设置的 key 有很多,所以可能命名不规范,也有可能会有缺漏,未来涉及到遗漏的地方就需要开发人员重新编码、发布才能满足需求,这是开发企业内部权限管理功能的痛点
利益相关者
- 甲方:开发人员,架构师,投资人
- 客户方:开发人员,产品经理,系统运营人员,老板
受益人 | 需求 |
---|---|
开发者 | 1、集成简单,少写代码;2、功能灵活,可以扩展;3、不要绑死,可以插拔;4、低代码侵入性,不影响业务代码 |
产品经理 | 1、随时可以增加对系统里面功能和数据的权限控制 |
系统管理员 | 1、希望能灵活对系统的权限进行配置,适合角色与员工结构,依照通用行业标准进行配置 |
开发者所属公司 | 1、低成本(划算);2、不要绑死 |
投资人 | 1、通过该项目的完整演示 RGCA 的架构设计过程;2、将此系统开源回馈到社区,以获得更多开发者的支持;3、进一步搜集开发者和企业用户的需求以进行下一步的开发 |
需求分类、排序、特征
提出人 | 分类 | 需求 | 优先级 | 分类 | KANO |
---|---|---|---|---|---|
产品经理 | - | 随时可以增加对于系统里面功能和数据的权限控制,不需要开发和发布系统 | - | 总体的意愿 | 基本型 |
产品经理 | 权限管理 | 可以对功能和页面进行组合成一个权限给角色,一次配置即可 | 一期 | 必需品 | 基本型 |
产品经理 | 数据权限 | 数据可以控制到新增、删除、字段级别的修改 | 一期 | 必需品 | 基本型 |
产品经理 | 数据权限 | 可以根据不同的角色、部门、进行查询数据字段的控制 | 推迟 | 必需品 | 反向型 |
产品经理 | 数据权限 | 可以控制不同的部门、角色查看的数据(比如只能看我所在部门的数据) | 推迟 | 必需品 | 基本型 |
系统管理员 | 权限管理 | 配置方便(使用一定的行业标准进行设计) | - | 必需品 | 无差异型 |
系统管理员 | 功能权限 | 可以对系统内的所有页面访问进行权限控制 | 推迟 | 必需品 | 基本型 |
系统管理员 | 功能权限 | 可以对系统内的所有页面的按钮进行权限控制 | 推迟 | - | - |
系统管理员 | 功能权限 | 对后台所有API请求进行权限控制 | 一期 | - | - |
开发人员 | 集成 | 集成简单,少写代码 | - | 对缺失物品所表现出来的欲望 | 期望型 |
开发人员 | 集成 | 功能灵活,可扩展 | - | 对缺失物品所表现出来的欲望 | 期望型 |
开发人员 | 集成 | 可插拔 | - | 必需品 | - |
开发人员 | 集成 | 低代码侵入性,不影响业务代码 | - | 必需品 | - |
客户公司高层 | - | 低成本 | - | 总体意愿 | - |
客户公司高层 | - | 可插拔 | - | 对缺失物品所表现出来的欲望 | - |
投资公司 | - | 低成本投入、快速可验证(敏捷,精益),少走弯路 | - | 总体的意愿 | - |
投资公司 | - | 通过该项目的完整演示 RGCA 的架构设计过程(时间为两天) | - | 必需品 | - |
投资公司 | - | 将此系统开源回馈到社区,以获得更多开发者的支持 | - | 对缺失物品所表现出来的欲望 | - |
投资公司 | - | 能够在企业生产系统中使用 | - | 必需品 | - |
将需求转化为目标(功能意图)
从系统的顶层角度:定义系统的形式和功能
确定功能意图(功能意图是由主要受益者,主要需求而推导出来的)
形式:通用的权限管理系统
to..by..using
为了...通过...使用
功能:无需代码埋点,通过UI配置改变资源(页面、按钮、数据、API)的可访问性,达到权限控制的目的
过程 操作 <= 工具
受益者 | 系统管理员 & 产品经理 |
---|---|
需求? | 对系统中受保护的资源进行权限保护 |
与解决方案无关的操作对象 | 受保护的资源 |
与利益相关的属性 | 可访问性,完整性 |
操作数的其他属性 | 可配置性,可访问性 |
与解决方案无关的过程 | 拦截/保护 |
无关过程的属性 | 准确性 |
将目标扩展为完整概念
在目标阶段提出了与解决方案无关的过程:拦截,一个模糊抽象的过程,没有说明由谁来拦截,以什么方式来拦截
与解决方案无关的操作对象:受保护的资源,一个抽象的对象,由需求导出了对象的分类,但是仍然没有特别具体,没有具体的场景
到了概念阶段需要提出具体解决方案过程:从解决方案不相关,到与解决方案相关
解决方案是帮助我们解决问题的,在目标阶段大致定义了需要解决什么问题,功能层面只是说明了产品的优势
具体的解决方案是在概念阶段提出的,它体现出如何把功能进行详细的描述,所以需要推导到到与解决方案相关的场面
受保护的资源以 API 为例进行推导,定义为 API Action,而拦截在 ASP .NET Core 中表现为 AuthorizationFilter
因为它是一个名词,不能代表一个过程,所以加上 ing 代表一个过程 AuthorizationFiltering
API Action 经过 AuthorizationFiltering 之后变成一个与解决方案相关的东西
在 ASP .NET Core Mvc 里面变成一个 Result,它是 AuthorizationContext 的一个属性
Result 有几种类型:Sucess,Forbiden,Challenge
受保护的资源除了 API Action 之外,还可以是 Entity,而所有 EF 的操作最终都放在 DBContext
DBContext 有一个 SaveChanges 的操作,以及一个 ChangeTracker 的属性记录了实体的所有状态
特化:变得更具体
从受保护的资源到 API Action 就是一个特化的过程,从目标到概念也是一个特化的过程
泛化:变得更抽象
从数据、页面、按钮、API 到受保护的资源就是一个泛化的过程
拦截的意图:保护资源
AuthorizationFilter 在 ASP .NET Core 中只能通过在 Action 上面打标签 Authorize 的方式进行拦截
这就是所谓的代码埋点,比如在 entity 上面打标签也是代码埋点
拦截的上一层是保护资源,拦截是保护资源的一种方式,需要提前定义受保护的资源
除了受保护的资源,对于所有资源需要动态保护,可以通过动态拦截的方式
动态拦截需要实现一个 DynamicAuthorizationFiltering,不能覆盖原有的功能
保护资源的上一层是安全,安全除了保护资源,还有很多其他的事情可以做,比如记录日志
审计日志会记录用户的所有访问记录,企业可以设置权限
通过这种方式可以不停地将需求往上一层寻找,一直达到最顶层
除了向上之外还可以向下寻找,延伸出整体概念,通过概念片段的组合,构成完整的整体概念
接下来对拦截这一过程进行展开,展开为一组必须得到执行的内部过程,针对每一个内部过程,选用特定的操作数、过程及工具对象对其进行特化,就可以得到相应的概念片段,这也是一个特化的过程
- 配置:系统管理员希望对后台所有 API 请求进行权限控制,所以首先需要知道有哪些 API,对每一个 action 需要可以配置
- 赋权:把角色赋权给用户
- 认证:用户认证之后有一个身份
- 授权:基于身份可以进行授权
将概念扩展为架构
- 价值通路与系统架构
- 层级分解
价值通路与系统架构
从资源变成权限,权限绑定给用户,用户进行登录,登录之后再进行授权
资源分为 ActionAccess 和 EntityAccess
EntityAccess 有 CanCreate,CanDelete,EntityName,Key 几个属性,以及每个字段是否允许修改 MemberAccess
同理 ActionAccess 有 Url,Name,DisplayName,Verb 几个属性
注册资源分为 Entity Explorer 和 API Explorer
Entity Explorer 通过 DbContext 进行扫描获取需要监听的实体进行注册
API Explorer 通过 IActionDescriptorCollectionProvider 注册 Action
授权有一个拦截器 AuthorizeFilter
ASP .NET Core Identity 有一个基于 Claims 的认证授权机制,它是一个 key:value 的数组
Clamis 属于 User 对象,User 对象属于 HttpContext
AuthorizeFilter 接收 Claims 和 ActionDescriptior,在 Claims 里面可以获取到 Action 的信息,所以两者有关联关系
对于赋权这一步需要定义权限 Permission 和角色,将权限和角色输入到赋权,产生一个角色权限 RolePermission
角色权限 RolePermission 是一个组合对象,包含角色与权限
权限和资源之间有一个包含关系,一个权限包含多个资源
至此完成了一条通路:给多个 Action 定义 key 之后,将 key 赋值给角色,角色绑定到用户,用户登录的时候可以获取到一个 Action 的列表,通过 AuthorizeFilter 来进行对比
授权由 AuthorizationContext 判断是否有权限
响应分为 API 响应 和 Entity 响应,针对不同的响应有不同的处理方式
对于 API 响应需要判断是否允许有权限,未认证返回401,无权限返回403
对于 Entity 响应需要 Claims 和 EntityAccessList,通过 Claim 和 AccessList 进行对比
用户登录之后得到 User 身份,发起请求产生 ActionRequest
ActionRequest 属于 HttpContext,最后会输入到 AuthorizeFilter
整个过程从上到下就是这样一个价值通路,并且已经包含了形式对象
从资源到权限,角色,再到角色和用户的绑定,再到授权整个体系,形成了系统架构
层级分解
首先从系统架构中找到实体对象:资源,权限,角色,用户
资源由 ResourceProvider 提供,分为 ActionResourceProvider 和 EntityResourceProvider
用户和角色使用 ASP .NET Core Identity 的 UserManager 和 RoleManager
ASP .NET Core Identity 只包含用户和角色,需要针对 Identity 做扩展,加上权限
UIprotron.Security.Core 负责管理资源和权限
UIprotron.Security.Identity 作为 Identity 的扩展,将资源和权限加入到 Identity 中,相当于一个适配层
UIprotron.Security.ActionAccess 和 UIprotron.Security.EntityAccess 分别负责 Action 和 Entity 的权限
大体上分为以下几部分:
- ASP .NET Core Identity:用户认证的库
- UIprotron.Security.Identity:Core 与 Identity 的集成组件
- UIprotron.Security.Core:对资源和权限的管理
- UIprotron.Security.ActionAccess:Action 资源发现和权限控制
- UIprotron.Security.EntityAccess:Entity 资源发现和权限控制
- UIprotron.Security.Store.EntityFramework:资源和权限的 EF Core 持久层
洋葱架构
- CoreAdapters:最核心最稳定的放最里面
- Application Security.Identity:应用层,Identity 的扩展
- Action Access
- Entity Access
- EfResourceStore
对每一层进行拆分,将功能拆分为 Core,Models,Store 和 EFStore