权限控制介绍
权限控制是一个古老的话题,你可能会想有没有什么权限设计方案可以满足所有的应用场景呢?
答案是没有,就像几乎所有问题一样,没有一种系统可以解决所有情况的,我们需要根据不同的场景和需求来设计不同的系统。
权限控制主要设计
用户
、
角色
、
组
、
对象
、
操作
、
权限
等对象。下面我先对这些对象做些解释,让大家先有个概念。然后我们再说说业界有哪些比较优秀的权限控制设计方案。
名词解释
用户
发起操作的主体。
角色
对于用户的抽象概念,类似于用户的属性,比如
管理员
就是一个角色。
组
可以是用户组,也可以是角色组,也可以是对象组。
对象
指操作所针对的客体对象,比如订单数据或图片文件。
操作
指作用于对象的动作,比如读取、写入。
权限
一般把对象和操作的对应关系和在一起叫权限,比如读取文件的权限,读取就是操作,文件就是对象。
权限控制设计方案
DAC
即
自主访问控制
,英文全称是
Discretionary Access Control
。
系统会识别用户,然后根据被操作对象(Subject)的权限控制列表(ACL: Access Control List)或者权限控制矩阵(ACL: Access Control Matrix)的信息来决定用户的是否能对其进行哪些操作,例如读取或修改。
而拥有对象权限的用户,又可以将该对象的权限分配给其他用户,所以称之为“自主(Discretionary)”控制。
这种设计最常见的应用就是文件系统的权限设计,如微软的NTFS。
DAC最大缺陷就是对权限控制比较分散,不便于管理,比如无法简单地将一组文件设置统一的权限开放给指定的一群用户。
MAC
即强制访问控制,英文全称是
Mandatory Access Control
。
MAC是为了弥补DAC权限控制过于分散的问题而诞生的。在MAC的设计中,每一个对象都都有一些权限标识,每个用户同样也会有一些权限标识,而用户能否对该对象进行操作取决于双方的权限标识的关系,这个限制判断通常是由系统硬性限制的。比如在影视作品中我们经常能看到特工在查询机密文件时,屏幕提示需要“无法访问,需要一级安全许可”,这个例子中,文件上就有“一级安全许可”的权限标识,而用户并不具有。
MAC非常适合机密机构或者其他等级观念强烈的行业,但对于类似商业服务系统,则因为不够灵活而不能适用。
ABAC
即基于属性的访问控制,英文全称是
Attribute-Based Access Control
。
ABAC被一些人称为是权限系统设计的未来。
不同于常见的将用户通过某种方式关联到权限的方式,ABAC则是通过动态计算一个或一组属性来是否满足某种条件来进行授权判断(可以编写简单的逻辑)。属性通常来说分为四类:用户属性(如用户年龄),环境属性(如当前时间),操作属性(如读取)和对象属性(如一篇文章,又称资源属性),所以理论上能够实现非常灵活的权限控制,几乎能满足所有类型的需求。
例如规则:“允许所有班主任在上课时间自由进出校门”这条规则,其中,“班主任”是用户的角色属性,“上课时间”是环境属性,“进出”是操作属性,而“校门”就是对象属性了。为了实现便捷的规则设置和规则判断执行,ABAC通常有配置文件(XML、YAML等)或DSL配合规则解析引擎使用。
总结一下,ABAC有如下特点:
-
集中化管理
-
可以按需实现不同颗粒度的权限控制
-
不需要预定义判断逻辑,减轻了权限系统的维护成本,特别是在需求经常变化的系统中
-
定义权限时,不能直观看出用户和对象间的关系
-
规则如果稍微复杂一点,或者设计混乱,会给管理者维护和追查带来麻烦
-
权限判断需要实时执行,规则过多会导致性能问题
既然ABAC这么好,那最流行的为什么还是RBAC呢?
我认为主要还是因为大部分系统对权限控制并没有过多的需求,而且ABAC的管理相对来说太复杂了。
Kubernetes
便因为ABAC太难用,在
1.8
版本里引入了RBAC的方案。
RBAC
即基于角色的访问控制,英文全称是
Role-Based Access Control
。
因为DAC和MAC的诸多限制,于是诞生了RBAC,并且成为了迄今为止最为普及的权限设计模型。
RBAC在用户和权限之间引入了
角色
的概念。
每个用户关联一个或多个角色,每个角色关联一个或多个权限,从而可以实现了非常灵活的权限管理。角色可以根据实际业务需求灵活创建,这样就省去了每新增一个用户就要关联一遍所有权限的麻烦。
由RBAC还可以做些扩展,比如下面两种方式。
角色继承
即Hierarchical Role,顾名思义,角色继承就是指角色可以继承于其他角色,在拥有其他角色权限的同时,自己还可以关联额外的权限。这种设计可以给角色分组和分层,一定程度简化了权限管理工作。
职责分离
即Separation of Duty,为了避免用户拥有过多权限而产生利益冲突,例如一个篮球运动员同时拥有裁判的权限(看一眼就给你判犯规狠不狠?),另一种职责分离扩展版的RBAC被提出。
RBAC模型
关系图
如图所示,每个用户关联一个或多个角色,每个角色关联一个或多个权限。权限是由一对对象和操作组成。
由此我们可以设计出主要的表结构关系。
表结构
用户角色表
userid | roleid |
---|---|
10001 | 20001 |
角色权限表
roleid | permissionid |
---|---|
20001 | 50001 |
权限表
permissionid | objecteid | operationid |
---|---|---|
50001 | 30001 | 40001 |
有了上面三张表,我们就有了完整的映射关系,还有些表是定义各个对象的,比如,
用户表
username | telephone | status | |
---|---|---|---|
zhangsan | zhangsan@xxx.com | 13411111111 | enabled |
角色表
roleid | rolename | rolecname |
---|---|---|
20001 | developer | 业务开发 |
操作表
operationid | name | describe |
---|---|---|
40001 | Query | 查询 |
对象表
objectid | name | type | desc |
---|---|---|---|
30001 | www.test.com | 域名 | test |
API网关权限控制模型设计
API网关的权限控制,我们采用RBAC模型。
API网关里面的对象其实只有两种,一种是前端元素,比如菜单、按钮;另一种是API接口,比如某个微服务的查询域名接口。
对于前端元素,操作对象只有在页面上显示或者不显示;而对于API接口,操作对象只有能调用或者不能调用。
根据上面的分析,操作对象其实就可以省略了,那么我们的RBAC模型就更简单了。可以简化为下图,
如果你觉得上面的结构不够用,那么你可以根据需要扩展,比如在用户和角色之间添加一层
用户组
,用于隔离用户和角色这两个对象。还可以给角色分层,不同的角色间可以设置归属关系。如下所示,
我们需要考虑一个问题,前端元素和API接口很多一一对应的,比如触发某个按钮会调用某个接口去执行动作,所以为了在控制台配置权限的时候能简单些,我们需要一个前端元素对象和API接口对象的映射表,有了这个表,如果在控制台为某个角色配置了前端元素权限,那么这个角色默认就有了对应的API接口权限了。
联动表
objectid | objectid |
---|---|
30001 | 30002 |
关键点设计
对象范围
我们前面说到的对象有前端元素和API接口,这两种对象对于所有微服务都适用,那么有没有其他类型的对象权限也可以在API网关中管理呢?
我先不回答这个问题,我们先拿一些对象分析分析看看。
我们先说
域名
这种对象,域名分一级域名和二三级域名,二三级域名是归属于一级域名的,所以在权限分配时,如果给角色分配了一级域名的权限,那么它下面的二三级域名也需要获取相应的权限,这里面涉及到对象归属和权限继承的问题。
我们再看看
证书
这种对象,它就没有归属关系,但证书的操作类型有增、删、改、查、启用、停用、续期等,操作类型比较多,而且有些是特别的,比如续期。
根据上面的分析,我们可以看出不同的对象,要么在对象本身上花样多,要么对应的操作类型花样繁杂,所以其他对象不适合在API网关里面做权限管理。
MVC框架权限控制
我们在学习微服务框架权限控制的时候也回头看看MVC框架的权限控制是怎么样的,对比下看看微服务框架有什么优势,这也叫
学而时习之,温故而知新
。
我们以Python开发框架Django为例来说明,在Django框架中自带表级别的权限控制模块,就是你可以控制数据库表的增、删、改、查操作,如果引入guardian这个插件的话还可以做到字段级别的权限控制。因为HTML/JS模块数据是由后端来填充的,所以Django可以控制前端元素,那它可以控制API接口的权限吗?
答案是不能,因为权限的控制只能在接口函数里面来做。由此,我们知道微服务框架的一个优势了哈。
缓存
在用户登录某微服务前端系统后,前端服务会从API网关获取需要的用户信息和前端元素,这种动作是用户登录才会触发,不会很频繁,权限控制服务可以hould住,用户每在前端操作一次通常就会触发一次接口调用,在调用前会问权限服务有没有权限调用,可以看到这个时间周期有点长,我们可以通过在网关内放一层缓存,里面缓存各个接口的权限信息,在权限服务有更新的时候会自动触发更新网关里面的缓存。缓存的数据结构可以设计成如下图所示,
开源方案
Shiro
推荐指数 ***
开发语言
Java
源码地址
https://github.com/apache/shiro
介绍
Shiro是一个强大的简单易用的Java安全框架,主要用来更便捷的认证,授权,加密,会话管理。Shiro首要的和最重要的目标就是容易使用并且容易理解。最主要的就是身份验证和权限控制这两个功能。
架构
组件
-
Authentication
:身份认证/登录,验证用户是不是拥有相应的身份。 -
Authorization
:授权,即权限验证,验证某个已认证的用户是否拥有某个权限;即判断用户是否能做事情,常见的如:验证某个用户是否拥有某个角色。或者细粒度的验证某个用户对某个资源是否具有某个权限。 -
Session Manager
:会话管理,即用户登录后就是一次会话,在没有退出之前,它的所有信息都在会话中;会话可以是普通 JavaSE 环境的,也可以是如 Web 环境的。 -
Cryptography
:加密,保护数据的安全性,如密码加密存储到数据库,而不是明文存储。 -
Web Support
:Web支持,可以非常容易的集成到 web 环境。 -
Caching
:缓存,比如用户登录后,其用户信息、拥有的角色/权限不必每次去查,这样可以提高效率。 -
Concurrency
:shiro 支持多线程应用的并发验证,即如在一个线程中开启另一个线程,能把权限自动传播过去。 -
Testing
:提供测试支持。 -
Run As
:允许一个用户假装为另一个用户(如果他们允许)的身份进行访问。 -
Remember Me
:记住我,这个是非常常见的功能,即一次登录后,下次再来的话不用登录了。
优点
-
用SecurityManager来管理其他组件,它是JavaBeans方式,是可兼容的,因此可以采用可配置的方式定制其他组件。
-
用户、角色、对象和权限信息是配置在INI文件中的,应该也可以采用数据库来存储,INI配置格式如下:
# ============================================================================= # Tutorial INI configuration # ============================================================================= [users] root = secret, admin guest = guest, guest presidentskroob = 12345, president darkhelmet = ludicrousspeed, darklord, schwartz lonestarr = vespa, goodguy, schwartz # ----------------------------------------------------------------------------- # Roles with assigned permissions # roleName = perm1, perm2, ..., permN # ----------------------------------------------------------------------------- [roles] admin = * schwartz = lightsaber:* goodguy = winnebago:drive:eagle5
权限模型可以是Role-Based Access Control,也可以是Permission-Based Access Control,即可以定义对象的层级关系,例如:
printer:query:JP0139829
Printer代表打印机,query代表查询操作,JP0139829代表具体的打印机,结合起来就是可以查询JP0139829这台打印机的信息。