[七年技术总结系列][理论篇]-RBAC权限模型由浅入深
权限部分将分两章介绍,第一章由浅入深介绍权限理论知识及应用,第二章介绍具体实现。后期再讲述中间件的使用时,还会插入一些权限内容,本质上属于中间件的应用。
权限模块是业务系统最常见、最基本的子集。本章假定了一个系统从最初简单的需求到逐渐成熟且完善的权限体系的实现过程。
阅读本章预计花费20分钟。
1. 最简单的权限模型
业务系统初期,需求简单,对于权限的内容本身并不复杂,我们假设权限部分仅有这样简单的需求:
能给用户赋予数据的增、删、改、查四种权限
分析此需求,权限的主体为用户,权限的内容有多种,关系为M - M,具体为:
用户模型:
public class User
{
public int UserId{ get; set; }
public string UserName { get; set; }
}
用户表Auth_User
字段 | 类型 | 说明 |
---|---|---|
*UserId | int | 用户ID |
UserName | varchar | 用户名 |
... | ... | ... |
权限枚举:
[Flags]
public enum Permission
{
Add = 1,
Update = 2,
Delete = 4,
Select = 8
}
权限表 Auth_Permission
字段 | 类型 | 说明 |
---|---|---|
*PermissionId | int | 权限ID |
Permission | varchar | 权限内容 |
用户-权限关系表 Auth_UserPermission
字段 | 类型 | 说明 |
---|---|---|
*UserId | int | 用户ID |
*PermissionId | int | 权限ID |
假如一个用户有增、改两种权限,那么关系表(Auth_UserPermission)可以存储为:
UserId | Permission |
---|---|
1 | 1 |
1 | 2 |
于是对于权限的基本操作我们可以进行归纳:
- 授权:INSERT 权限表 (用户ID,权限的具体值)
- 校权:EXISTS 权限表 UserID==用户ID AND Permission==要判断权限的具体值
我们留意到对于Permission的枚举定义,值使用了对2的幂运算的值:
幂运算 | 十进制 | 二进制 | 十六进制 |
---|---|---|---|
2^0 | 1 | 0001 | 0x01 |
2^1 | 2 | 0010 | 0x02 |
2^2 | 4 | 0100 | 0x04 |
2^3 | 8 | 1000 | 0x08 |
这么定义是有好处的,对于Auth_UserPermission的表存储可以节省存储空间,并且程序便于处理,譬如:
如果UserId=1的用户拥有Add、Select权限,Auth_UserPermission表原本应该存储两条记录:
- (1,1)
- (1,8)
现在,可以考虑更简单的存储方式
- (1,9)
这表示:
Permission.Add | Permission.Select
等价于
1 按位或 8 ( 1 | 8 )
等价于
9
而对于权限的判断,则使用存储的权限值按位与要进行校权的值是否等于要进行校权的值来判断
譬如判断用户是否拥有Delete权限,则使用9按位与4是否等于4来进行判断,用C#的三目运算来表示为:
9 & 4 == 4 ? "有权限":"无权限"
这样被标记有Flags特性的枚举在.Net框架中遍布各种基础类库,譬如反射中的BindFlags枚举。本身属于基础知识,由于不常应用所以容易被忽视,在权限中属于应用小技巧。还有人质疑这么存储会有性能问题,在后面章节讲到优化时,再行讨论。
于是我们对使用了小技巧的新的权限基本控制再次进行归纳:
- 授权:INSERT 权限表(用户ID,所有拥有权限的按位或值)
- 校权:EXISTS 权限表(UserID == 用户ID AND Permission & 要判断权限的具体值 == 要判断权限的具体值)
2. 基于角色的基本权限控制
随着业务系统的发展,业务系统有了第一次升级机会,并附带了一个新的权限需求:
系统需要满足一类职位的人拥有相同的权限
按照第一节的内容,这个需求其实不用做任何变化一样可以满足,但是问题在于负责授权的人“太累了”,对于每一个用户,我们可能都要做一遍授权的操作。
为了解决这个问题,我们引入角色这一基本单元,角色是一种抽象,可以具体到业务场景的类似职位、身份等概念。
角色模型设计:
public class Role
{
public int RoleId { get;set; }
public string RoleName { get;set; }
}
角色表设计Auth_Role:
字段 | 类型 | 说明 |
---|---|---|
*RoleId | int | 角色ID |
RoleName | varchar | 角色名称 |
基于角色的基本权限控制的原则是:
- 简化用户权限的操作;
- 权限操作的对象从用户变更为角色;
- 不能对单一用户做权限操作,仅对角色做权限操作,每个需要权限的用户,都拥有至少一个角色;
角色与用户的抽象关系表现为M-M,这表示:
- 一个用户可以拥有多个角色;
- 一个角色下有多个用户;
具体到业务可以是一个人可以有多个职位;一个职位下有多个人;
针对此设计,我们需要做以下操作:
- 从系统中删除掉原来的Auth_UserPermission关系;
- 新增Auth_UserRole(UserId,RoleId)的关系;
- 新增Auth_RolePermission(RoleId,Permission)的关系;
假定业务系统有这样的职位列表:
RoleId | RoleName |
---|---|
1 | 总裁 |
2 | 开发总监 |
假设用户ID等于1001的用户职位为总裁兼开发总监,那么关系表Auth_UserRole可以存储为:
UserId | RoleId |
---|---|
1001 | 1 |
1001 | 2 |
业务约定:总裁有增、删、改、查四个权限,开发总监则有增、查两个权限,那么关系表Auth_RolePermission可以存储为:
RoleId | Permission |
---|---|
1 | 15( = 1 | 2 | 4 | 8 ) |
2 | 9 |
我们对给予角色的基本权限控制操作再次归纳为:
- 授权:给角色添加权限(INSERT Auth_RolePermission),给用户添加角色(INSERT Auth_UserRole)
- 校权:应当是拿出用户所有的角色,并再次拿出这些角色的权限做并集,并DISTINCT 权限并集为权限集合,判断权限集合是否含有需要校权的权限
3. 基于角色并含有用户组概念的权限控制
春去秋来,业务系统迎来了第二次升级机会,并包含以下新的权限需求:
所有部门的开发岗位拥有相同的增、查权限
基于第二节的系统升级,解决此需求我们会有临时的做法:做一个角色,给所有开发岗的同事赋予这个角色。
这样的临时做法的确解决了我们的问题,但这里有几个问题,函待解决:
- 系统没有部门的对应抽象;
- 一旦其中一个部门的开发岗同事拥有的权限有变动,我们需要新建角色,并重新授权;
针对此两个问题,我们引入一个新的模型:用户组(UserGroup),用户组的概念在业务系统中,可以具体为:部门、小组、团队等
用户组模型设计:
public class UserGroup
{
public int UserGroupId { get; set; }
public int ParentId { get;set; } //留意此字段,将在本节末尾阐述
public string UserGroupName { get; set; }
}
用户组表Auth_UserGroup设计:
字段 | 类型 | 说明 |
---|---|---|
*UserGroupId | int | 部门ID |
ParentId | int | 上级部门ID |
UserGroupName | varchar | 部门名称 |
基于角色并含有用户组概念的权限控制有以下特点:
- 再次简化了用户权限的操作;
- 用户可以拥有角色;用户组也可以拥有角色;
- 权限的操作对象依旧为角色,不可对用户、用户组进行权限操作;
用户与用户组的关系表现为多对多,这表示一个用户可以属于多个用户组,一个用户组下可以有多个用户,具体到业务可以描述为:一个人可以在多个部门,一个部门下可以有多个人;
用户组与角色的关系表现为多对多,这表示一个用户组的所有用户可以拥有相同的多个角色,一个角色下有多个用户组,具体到业务可以描述为:同一个部门的人可以拥有多个相同的职位;
为了实现此设计,我们需要做以下新的操作:
- 新增Auth_UserUserGroup关系;
- 新增Auth_UserGroupRole关系;
假设系统拥有这样的部门列表:
UserGroupId | UserGroupName |
---|---|
1 | 总裁办 |
2 | 前端开发部 |
3 | 中台开发部 |
4 | 人力资源部 |
5 | 保安部 |
假设用户ID为1101的用户既是前端开发部的开发总监,又是中台开发部的开发总监;中台开发部、前端开发部的所有同事本质都是开发,且所有开发部的同事都有增、查的权限,那么:
用户-用户组Auth_UserUserGroup关系表可以存储为:
UserId | UserGroupId |
---|---|
1101 | 2 |
1101 | 3 |
新增角色:开发
RoleId | RoleName |
---|---|
6 | 开发 |
Auth_RolePermission新增记录:
RoleId | Permission |
---|---|
6 | 9 |
Auth_UserGroupRole关系表可以存储为:
UserGroupId | RoleId |
---|---|
2 | 6 |
3 | 6 |
这样,我们就满足了本节提出的需求。
另外要注意到的是用户组的ParentId字段,不要轻视这个简单的树状设计,实际应用中根据业务场景会有各种不同的问题,譬如不良的SQL导致DB层面做了递归查询、上级部门权限与下级部门权限的继承关系,但这本质属于业务需求,不再赘述
4. RBAC权限模型
现在,系统经过3次升级,已经有了较为完备的权限体系,我们解决了大部分问题。
但是我们也注意到,所有的有关于权限的定义仅仅围绕着增删改查这一类权限控制。假如系统现在需要多控制一部分权限内容,我们就有些捉襟见肘了。
简单来说,我们的权限模型设计对于扩展支持不够
譬如,业务系统初期对系统的菜单可见性有权限控制,随着系统迭代,可能出现对文件的可操作性也需要有权限控制,这是很正常的事,显然,依照我们的设计,系统无法满足。
回顾1、2、3节的升级内容,我们的问题其实是由单一权限元素变更为多元权限元素,如果我们能重新将被控制元素变更为单一元素,我们之前的设计则不用变更。
为了解决这个问题,我们对各种权限元素进行抽象,譬如文件访问权限和菜单访问权限。抽象为如下图内容:
现在,权限的Root节点变成了Permission这个抽象,它没有具体的意义,但他将各类权限集中在了一起,使得多种权限元素重新集中在单一Permission这个抽象元素上,再次揉入到我们的系统中,如下图:
这就是权限系统的RBAC完成模型。
至此,借助RBAC模型,我们完成了权限模块的理论设计,它能满足大量权限控制场景,也是业界惯用的手段,RBAC模型是一种权限模型的总结和归纳,市面上能见到的各种权限控制,都与RBAC沾边,也就是说,掌握RBAC,就掌握了阅读各种系统权限设计的基础,有了理论支持。
不过值得注意的是,虽然我们有了理论基础,但实际应用中,我们还要做一些扩展内容。
譬如说权限历史,权限模块属于敏感内容,是系统的中枢所在,严谨的权限模块肯定是不会对操作进行Delete的,而是Fake Delete以保留历史。上文中这样的设计为此提供了方便,当用户的权限发生变更时,我们只需要对关系做Fake Delete即可。当然,关系本身需具备IsFakeDeleted属性。
下一章节将介绍dotnet core的具体实现。
[七年技术总结系列][理论篇]-RBAC权限模型由浅入深的更多相关文章
- RBAC权限模型——项目实战(转)
一.前言 权限一句话来理解就是对资源的控制,对web应用来说就是对url的控制,关于权限可以毫不客气的说几乎每个系统都会包含,只不过不同系统关于权限的应用复杂程序不一样而已,现在我们在用的权限模型基本 ...
- (转)RBAC权限模型——项目实战
一.前言 权限一句话来理解就是对资源的控制,对web应用来说就是对url的控制,关于权限可以毫不客气的说几乎每个系统都会包含,只不过不同系统关于权限的应用复杂程序不一样而已,现在我们在用的权限模型基本 ...
- RBAC权限模型及数据权限扩展的实践
话说大家对RBAC权限模型应该是耳熟能详了.但真正用的好的并不多.并且原始的RBAC模型并不包括数据权限的管理,网上也差点儿没有相关的文章可以參考.本人经过几个项目的实战,在其基础上扩展出一套可行的. ...
- RBAC权限模型——项目实战
RBAC权限模型——项目实战
- JWT与RBAC权限模型
JWT JWT是什么? Json web token (JWT)是为了网络应用环境间传递声明而执行的一种基于JSON的开发标准(RFC7519),该token被设计为紧凑且安全的,特别适用于分布式站点 ...
- RBAC 权限模型
RBAC 0 模型 最基本的 MySQL 脚本,没有建立外键约束. /* Navicat Premium Data Transfer Source Server Type : MySQL Source ...
- 基于角色访问控制RBAC权限模型的动态资源访问权限管理实现
RBAC权限模型(Role-Based Access Control) 前面主要介绍了元数据管理和业务数据的处理,通常一个系统都会有多个用户,不同用户具有不同的权限,本文主要介绍基于RBAC动态权限管 ...
- 权限管理系统(四):RBAC权限模型分类介绍
RBAC是Role-BasedAccess Control的英文缩写,意思是基于角色的访问控制.RBAC认为权限授权实际上是Who.What.How的问题.在RBAC模型中,who.what.how构 ...
- .Net Core微服务系列--理论篇
微服务的由来 微服务最早由Martin Fowler与James Lewis于2014年共同提出来的,但是微服务也不是一个全新的概念,它是由一系列在实践中获得成功并流行起来的概念中总结出来的一种模式, ...
随机推荐
- stm32 新建工程
先新建六个文件夹. Consis:启动文件等 Fwlib:inc.src文件夹 Hardware:存放驱动 Mdk:工程文件 User:main函数等
- HTML定位——绝对定位和相对定位、固定定位
1.绝对定位 绝对定位指的是通过规定HTML元素在水平和垂直方向上的位置来固定元素,基于绝对定位的元素不会占据空间. 绝对定位的位置声明是相对于已定位的并且包含关系最近的祖先元素.如果当前需要被定为的 ...
- 2019年9月末周java面试总结
不知不觉离职已经2个月了,这周开始投简历找工作,本来也做好了被打击的心理准备了,毕竟这么久没敲代码,也没怎么准备,基本上是属于裸面. 总结一下简历投递情况: 不知道是简历写得太敷衍,还是要求太高,总之 ...
- C#深入学习笔记 - 可空类型与构造函数默认参数
在实际开发中或许可能会遇到某个属性需要提供一个默认参数,如果该参数是引用类型的话,可以通过 使用 null 来表示未知的值,但如果是int或 其他值类型的话就有点不好办了,因为如果需要一个int或fl ...
- SpringBoot起飞系列-Web开发(四)
一.前言 从今天你开始我们就开始进行我们的web开发,之前的一篇用SpringBoot起飞系列-使用idea搭建环境(二)已经说明了我们如何进行开发,当然这是搭建起步,接下来我们就开始进行详细的开发, ...
- SpringBoot 定时任务实现方式
定时任务实现的几种方式: Timer:是java自带的java.util.Timer类,这个类允许你调度一个java.util.TimerTask任务.使用这种方式可以让你的程序按照某一个频度执行,但 ...
- 对vue nextTick深入理解-vue性能优化、DOM更新时机、事件循环机制
一.定义[nextTick.事件循环] nextTick的由来: 由于VUE的数据驱动视图更新,是异步的,即修改数据的当下,视图不会立刻更新,而是等同一事件循环中的所有数据变化完成之后,再统一进行视图 ...
- Kafka常用命令合集
在上一篇文章<Linux安装Kafka>中,已经介绍了如何在Linux安装Kafka,以及Kafka的启动/关闭和创建发话题并产生消息和消费消息.这篇文章就介绍介绍Kafka的那些常用的命 ...
- Gin框架介绍及使用
Gin是一个用Go语言编写的web框架.它是一个类似于martini但拥有更好性能的API框架, 由于使用了httprouter,速度提高了近40倍. 如果你是性能和高效的追求者, 你会爱上Gin. ...
- css 精灵图的使用
精灵图的使用 1.给一个容器定义一个大小(宽高) 2.引入背景图 3.定位到自己你想要的图片位置 例如: background-position: 0 0; background-position ...