序
今天来聊聊我工作中的一些经历和感想,最近一年我一直在从事基础框架中核心授权方向的工作,断断续续经历过大概一年多的迭代开发,将整个授权体系从我接手时仅仅存在角色,用户和权限之间的简单关联关系,拓展到了支持组织与角色,互斥角色,角色组以及正在做的将原来rbac
授权模型逐渐转向更为灵活的abac
模型,其中走过很多弯路,经历各种问题,但庆幸自己还是坚持了下来,现在回想,整个过程还是有收获的,所以准备以此文章作为自己的总结。
本文会先介绍一下传统的权限模型,其中会重点介绍rbac
和abac
授权模型,之后会说明我当前所开发的模型和这两个模型的差异,最后考虑一下对未来的展望和优化方向。
授权模型
在最原始的“上古”时期,授权模型是一个十分简单的东西,简单到用户和权限之间就是之间关联的,这些对应关系可以存在于数据库,也可以存在于配置文件,这样当用户登录进来之后,只需要去查询当前用户对应的权限即可,没有其它错综复杂的关系在里面。
这样的授权模型主要包括ACL
、DAC
、以及MAC
模型,但是在目前互联网大环境下,由于用户数量过多的关系,这样的模型很明显是不适合的,所以我会简单提一下这三种模型。
传统授权模型
ACL
ACL
全称叫Access Control list
(权限控制表),用来描述权限规则或用户和权限之间关系的数据表,这是一个非常简单的关联关系,每一个资源都会配一个列表,用来记录哪些用户可以对这项数据资源执行CRUD
等操作,当用户试图访问这项资源时,首先会检查这个列表中是否有关于当前用户的访问权限,从而确定当前用户可否执行相应操作,其基本架构图如下:
DAC
DAC
全称为Discretionary Access Control
(自主访问控制),系统会识别用户,然后依据操作对象的权限控制列表或者权限控制矩阵的信息来决定用户是否能对其进行哪些操作。DAC
和ACL
的最主要的区别就是自主这两个字,当一个用户拥有该资源的权限的时候,就可以将该资源的权限再分配给其它用户,这一过程称之为自主。
从上面可以看出,DAC
模型其实就是ACL
模型的扩展,其基本思想还是用户与权限之间的直接关联关系。只不过它开始拥有现代授权模型的雏形,也就是一个拥有某个资源权限的用户,可以将该资源再授权给其它用户。这一点和之后的模型是十分相似的。
DAC
模型有一个比较常用的场景是传统的Unix
访问控制模型,例如Windows
文件系统中的访问控制模型。
在Windows
的授权模型中,还提到了组的概念,可以将一个资源授权到某个组上面,然后用户如果属于这个组下面,那么就会拥有对应的权限,说到这里,如果有熟悉权限模型的朋友,应该可以想到用户和角色之间的关联关系。其实不论是组还是角色,都是为了更好的管理和分配权限而在最原始的ACL
模型上进行改进。
MAC
MAC
模型,全称Mandatory Access Control
(强制访问控制),MAC
是为了弥补DAC
模型中权限控制过于分散的问题而诞生的,在MAC
的设计中,每一个对象都有一些权限标识,而每一个用户同样也会有一些权限标识,这样当用户能否对该对象进行操作取决于双方的权限标识的关系,而这个限制判断通常是由系统硬性限制的。访问时,系统先对用户的访问许可级别和资源对象的密级进行比较,再决定用户是否可以访问资源对象。用户不能改变自身和资源对象的安全级别,只有系统管理员或者管理程序才能控制资源对象和用户级别。
举个例子,在影视作品中我们经常能看到特工在查询机密文件的时候,屏幕提示“无法访问,需要一级安全许可”,文件需要的一级安全许可的权限标识当前用户不具有。
关于DAC
模型和MAC
模型,我这里再做一个详细对比,以DAC
模型为例,只要有一个用户拥有某个资源的权限,那么该用户就可以将资源对应的权限分配给其它用户,但是这其实是一个比较危险的情况,如果该用户通过不同用户分配权限,最后拼接出一个拥有一个超级权限的用户,那么将有可能影响到系统安全,而MAC
模型中,我们可以对用户打上等级标签,这样即使有高级别用户给这个低级别用户分配高级权限,但是因为其级别原因,也是无法分配成功,从而提高系统安全性。
常用授权模型
RBAC模型
RBAC
模型是目前比较常用的被大众所接受的模型,其全称是Role-Based Access Control
(基于角色的权限控制),通过角色关联用户,角色关联权限的方式间接赋予用户权限。
该模型相对于直接将权限赋予用户,有着很大的优势,传统用户直接关联权限,每新建一个用户的时候,都需要重复用户赋权操作,而将用户和权限解耦之后,新建的用户只需要和角色进行绑定即可,无需其它过多的操作,简化操作流程,同时还可以对用户进行批量的权限调整,既大幅提高了权限的调整效率,也减少了因为单独对用户赋权导致漏调或者误调权限的可能。RBAC
权限模型图如下:
在现有RBAC
模型基础上,业内人员总结并升级了部分模型,并总结出了RBAC1
、RBAC2
以及RBAC3
模型。
RBAC1模型
我们称上面提到的权限模型为RBAC0
模型,相对于RBAC0
模型,在RBAC1
模型中,引入了角色继承的概念,具有明显层级的角色之间,使用角色继承的方式避免赋错权限的问题出现,例如开发人员,开发组长,技术经理,这些角色之间具有明显的层级关系,那么通过引入角色继承的方式,可以将角色对应的权限限定在开发这一块,不会出现错赋到其它业务权限的问题。其模型图如下:
个人看来,角色继承的方式可能在某些方面有着很好的效果,但是目前我还没有想到存在的意义,毕竟现在角色都是可以和组织进行绑定的,所以如果出现上面的,开发人员,开发组长,技术经理之间的层级关系,那么完全可以考虑将这些用户分别绑定对应的组织,而组织对应的角色早已预设好,这样也可以避免角色赋错的情况发生。
RBAC2模型
RBAC2
模型依然基于RBAC0
模型进行拓展,它对角色增加了一些限制,例如:角色互斥、基数约束以及先决条件约束等。
- 角色互斥:同一用户不能分配到一组互斥的角色集合中的多个角色,互斥角色是指权限相互制约的两个角色。例如:银行系统中,报价业务员和复核业务员就不能是同一个人,避免出现私自报价成交的可能。
- 基数约束:一个角色被分配的用户数量受限,在一些重要的场景下,例如公司高层,因为公司高层的数量是一定的,所以一个角色只需要分配给这些高层即可,这样当其他用户赋予高层角色时,就会提示角色已经绑定足够多的用户,无法再次绑定,提升系统安全性。
- 先决条件角色:一个用户如果想要获取高层次角色,那么必须先获取低层次角色,例如先拥有开发人员角色,才能拥有开发组长角色。
- 运行时互斥:一个用户在拥有两个角色的时候,在运行时不能同时激活这两个角色,例如:同一个用户拥有多个角色,角色的权限有重叠,那么以较大的权限为准。
总的来说,RBAC2
模型在一定程度上起到了作用,部分思想是比较符合业务场景的。
RBAC3模型
RBAC3
模型并没有提出任何的建设性的新模型,而是将上面提到的三种模型做了融合,充分综合三种模型的所有特点,将权限模型提到了一个新的高度。
ABAC模型
ABAC
模型全称是Attribute-Based Access Control
(基于属性的权限控制),是一个相对于RBAC
模型来说,比较小众的模型,但是该模型却是一个十分灵活,功能强大的模型。
RBAC
模型虽然应用广泛,但是在某些条件下却是无法满足应用场景的,例如业务员1
和业务员2
都属于业务员角色,都有查看客户订单的权限,但是业务员1
属于上海地区,业务员2
属于北京地区,而上海地区的业务员是不能查看北京地区的业务订单的,北京地区业务员同理。面对这样的需求,RBAC
权限模型是无法满足的,可能可以做的方式是创建两个角色,分别是上海地区角色和北京地区角色,这样当遇到需要过滤的数据的时候,就可以利用角色的不同,来获取到角色对应地区的数据。
而利用ABAC
模型就可以较为简单地实现上述需求,上面场景中,地区是订单的一个属性,需求就是针对这个的确属性来做订单的查询范围控制,这种控制权限的方式就是ABAC
权限模型。
ABAC
模型中,通过将属性分为四类:
- 用户属性(如用户的年龄)
- 环境属性(如时间,地点)
- 操作属性(如读取)
- 对象属性(操作的对象,读取订单,读取资源)
利用这四种属性,来构建相对应的规则,通过动态计算一个或一组属性是否满足某种规则来达到鉴权的效果。
例如规则:“允许所有员工在工作日时间自由进出公司”,这里所有员工就是用户属性,工作日时间就是环境属性,进出是操作属性,公司就是对象属性。当某个员工在某个时间来到公司的时候,就可以通过采集现有的属性来匹配这条规则,当规则匹配成功的时候,说明拥有该权限,否则没有。
ABAC
模型非常灵活,但是实现也是非常的难,其中涉及到了逻辑的动态执行,数据的动态过滤等,有兴趣的可以去这里看一下相关的模型实现。
拓展
前面我们简单提了一下当前现有的权限模型,比较了一下各个模型的缺点和优点,相信读到这里的对权限模型都有了一定的了解,那么接下来我要介绍的,就是我目前正在做的权限模型,以及我所做的权限模型借鉴了哪些东西,有哪些不足,因为涉及工作方面,所以我们对权限模型进行抽象,不会讲解具体实现,仅仅讲解相关思想。
在聊新的权限模型之前,我先提几个问题,看看你能否回答出来。
- 在讲
ABAC
模型的时候,我有提到过ABAC
模型可以解决RBAC
模型中的一些弊端,而这些弊端是一直存在于RBAC
模型中的,但因为历史原因,在真实开发条件下,我们是不能轻易将现有权限模型进行替换的,因为带来的一系列问题我们往往负担不起,那么如何基于现有RBAC
模型,构造出一个符合ABAC
模型的全新模型呢? - 如果用户、角色以及权限之间的关联关系过多的话,数据库是比较消耗存储空间的,同时在鉴权方面也会花费较多的时间去处理鉴权,那么如何通过某种方式降低数据存储方式,同时提高鉴权效率呢?
RBAC模型向ABAC权限模型过渡
在ABAC
权限模型中,更加强调的是属性的概念,弱化了角色的概念,而RBAC
权限模型中起作用的往往就是角色,那么将RBAC
模型向ABAC
模型过渡的话,其实就是逐渐弱化角色,强化属性的概念。为此,我在我的开发框架中提出了权限属性的概念,相对于ABAC
模型通过预先定义一定的规则,然后利用属性去匹配规则,我们模型中是给角色添加了一些属性,然后利用特定的属性对应的属性值去寻找对应的角色。其架构图如下:
如图所示,我给每一个角色都定义了四个属性,分别为时间、地点、项目和天气,角色和对应属性值存在关联关系,当传入的属性值不同,得到的角色就不同,以此达到控制权限的目的。
利用这样的方式,我们也可以解决上海地区业务员只能查看上海地区业务数据,北京地区业务员只能查看北京地区业务的数据。例如我们给角色绑定一个地区的属性,后端接受该属性对应的属性值,这样当前端传递的属性值是北京的时候,就可以返回北京的数据,而传递的是上海的时候,就可以拿到上海的数据,利用这样的方式解决传递角色到后端查询的问题。
目前上面的给角色添加属性的方式,已经在我们基础框架中采用,预计随着框架的发布,将会有几十个系统使用到这样的技术。
采用位运算鉴权
由于我们框架后期会采用多租户的方方式对外提供基础功能服务,这样业务开发方只需要关注自身业务对应的功能,非业务的菜单,角色以及资源的管理将交由单独的服务进行支持,这样各司其职的方式,可以节约业务方在基础服务上所消耗的时间以及硬件资源,提高开发效率。而如果要支持多租户的方式对外提供服务,那么到时候将会有很多的系统接入我们基础服务系统,那么系统的性能瓶颈可能存在于数据存储,以及数据响应方向,所以需要提前预知这样的问题,并能够想到对应的解决方案。
为此,我参考linux
系统中的鉴权方式,通过采用位运算的方式提高鉴权效率,同时降低存储空间。
熟悉linux
系统的人都知道,我们对linux
系统中的用户或者文件授权的时候,往往使用类似chmod / 755
这样的方式,命令中的775
、755
、777
等背后的对应原理就是采用位运算思想来进行的。
linux
中将读,写和执行分别对应了二进制中的0100
、0010
和0001
,十进制就是4
、2
、1
。这样当要给对应文件或用户赋权的时候,只需要将需要权限之间进行按位与运算之后转为十进制就是最终的权限对应的数字。例如十进制7
表示的就是4
&2
&1
之后得到的十进制数字,背后对应的权限就是读、写和执行。
位运算鉴权应用
一个基本类型为long
的十进制数字,它可以看作是由64
位0
或1
组成的二进制。我们用这些二进制对应的位置来表示每一个资源,例如菜单管理对一个的资源有10
个,那么就可以利用一个long
类型,分别将其低10
位全部置为1
,表示这些位置已经存在资源。如果一个long
类型已经无法存储当前资源(即某个菜单对应的资源数已经超过了64
个),那么就需要新增一个long
类型来存储多出来的数据,所以为了保证这些资源的全局唯一性,我们就考虑使用格式为 (idx,pos)
的方式来存储数据,这样整个系统的资源数据便可以用一个集合{(idx0, pos0), (idx1, pos1), ……}
来存储。其中idx
表示的是第几个long
类型整数对应的下标,pos
表示的是当前long
类型对应的二进制数所处的位置。
我们举个例子,假设当前集合中对应的十进制数是{1}
,那么它表示的就是(0, 0)
,集合{-1, 1}
表示的就是idx=0,pos∈[0,1,2,……63]
和idx=1,pos=0
对应的资源数据。
而角色对应的权限也是可以采用这样的方式进行存储,这样之后进行资源鉴权的时候,只需要对当前资源所处的位置和对应角色拥有的资源进行按位与运算,如果位运算的结果是当前资源对应的位置,那么就代表有权限,否则就是没有权限。整个位运算鉴权方式,可以依照下面的图示进行:
如图所示,我们设置的用户A
对应的角色是角色A
,其拥有菜单管理、角色管理等资源权限,最终通过位运算方式存储在数据库中的数据如上图中所示,此时如果用户发送的请求访问菜单管理,而请求的是删除数据,查询删除数据对应资源中的位置,我们可以知道是第4
位,对应二进制是0000……1000
,转换成十进制是8
,所以我们拿着8
去和数据库中存储的15
进行按位与操作,最终的到的结果还是8
,表示当前资源有权限访问,而如果用户请求的资源对应的如果不是8
是16
,那么按位与运算之后的结果是0
,那么说明没有权限。
以上操作就是关于位运算鉴权的基本思想,通过这样方式可以达到节省存储空间和提升鉴权效率的目的,但是实现起来可能没有普通通过字符串匹配的方式来的快速,所以如果在资源比较小的情况下,还是建议使用普通的方式进行,毕竟无论是开发效率还是代码易理解程度,都是比位运算来得方便。
总结
本文主要针对常见的授权模型进行了概要的计算,在此基础之上,穿插了自己开发中针对这些模型的灵活运用构建符合自己的权限模型,通过这段时间关于权限模型的学习和开发,我能够越来越理解抽象的概念,如果你无法针对特定问题进行抽象建模,那么你创建出来的东西可能仅仅为了处理一个单独的特定问题而开发出来的,无法适用到大多数场景,那么这样的东西其实是没有具体作用的。