用户权限(IAM)可以说是平台化建设最基础的服务,该服务建设的好差直接关系到平台化建设的成败。 《平台化服务的基石:用户认证模型设计》 一文介绍了用户及认证体系的设计,本文继续聊一聊权限相关的模型设计。
基本概念
常见的权限模型有:
-
ACL(Access-control list):为简单的权限控制场景设计的模型,多见于文件系统的权限设置,详见: https://en.wikipedia.org/wiki/Access-control_list
-
RBAC(Role-based access control):基于角色的访问控制模型,引入了角色解耦权限与用户的关系,实现职责分离。这是Web应用系统最主流的基础权限模型,详见: https://en.wikipedia.org/wiki/Role-based_access_control
-
ABAC(Attribute-based access control):基于属性的权限访问控制模型,与RBAC相比它将角色细化为属性,以实现更多维度更细粒度的权限控制,详见: https://en.wikipedia.org/wiki/Attribute-based_access_control
接口权限控制
需要对一些服务接口做相应的权限控制,不同用户有不同的操作权限。

如上图,我们实现了一个最简单的基于RBAC的权限模型。在RBAC模型基础上添加了对租户、应用的支持,不同租户及应用都可以有独立的角色及资源数据。
数据权限控制
需要在数据维度上做权限控制,比如A用户只能查看自己组织的数据,再比如项目经理可以查看公司所有项目概览并管理自己所属产品线项目的信息。

为支持数据级权限控制我们对模型做了比较大的调整,引入了群组(Group)及角色定义(RoleDef),并对角色(Role)及权限(Permission)模型做了调整:
-
群组(Group):树型结构,业务表达上可对应于公司行政组织、虚拟项目组、产品用户群组等。它的编码要能直接体现出上下级关系,以避免递归查询,业务编码对应于各业务系统自定义的组织编码,用于数据级权限过滤
-
角色定义(RoleDef):此模型等同于上一小节的角色模型,用于记录角色的定义。如系统管理员、部门经理、财务主管
-
角色(Role):此模型由群组和角色定义组合而成,对外连接账号和权限,群组和角色定义可二选一
-
权限(Permission):此模型加了是否排除字段,默认为false,即表示有对应资源的权限,当其为true时表示对此资源没有权限,用于权限的强制覆盖。比如某个用户有2个角色,角色Role1有资源Res1、Res2的权限,角色Role2有资源Res3的权限但同时明确不能有Res1的权限,这种“不能有”优先级最高,故该用户拥有Res2、Res3的资源权限
我对上文需求做扩展:
-
项目经理可以查看公司所有项目概览
-
项目经理可以管理自己所属产品线项目的信息
-
A产品线的项目经理不能查看公司所有项目概览(比如A产品线刚从其它公司收编过来,权限受限)
-
A产品线下的所有员工(无论什么职位)都可以访问产品A
那么对应的数据大概是:
编码 | 定义名 |
---|---|
proj_mgr |
项目经理 |
… |
… |
编码 | 群组名 |
---|---|
10000 |
产品线 |
1000000001 |
产品线A |
1000000002 |
产品线B |
… |
… |
主键 | 名称 | 路径(后文使用URI) |
---|---|---|
res1 |
所有项目概览 |
/mgr/proj/overview |
res2 |
单个项目信息 |
/mgr/proj/{projId}/** |
res3 |
产品A信息 |
/proj/A/** |
主键 | 关联群组 | 关联角色定义 |
---|---|---|
role1 |
proj_mgr |
|
role2 |
1000000001 |
proj_mgr |
role3 |
1000000001 |
|
role4 |
1000000002 |
这里要注意,我们定义了三个角色:
-
role1 只关联了角色定义,表示的是所有项目经理
-
role2 关联了群组及角色定义,表示的是产品线A的项目经理
-
role3 只关联了群组,表示的是产品线A的所有账号(用户)
-
role4 只关联了群组,表示的是产品线B的所有账号(用户)
关联角色 | 关联资源 | 是否排除 |
---|---|---|
role1 |
res1 |
false |
role1 |
res2 |
false |
role2 |
res1 |
true |
role3 |
res3 |
false |
最后我们看下角色对应的权限:
-
所有项目经理(role1)可以查看公司所有项目概览(res1)
-
所有项目经理(role1)可以管理自己产品线项目的信息(res2),逻辑是:
-
通过role3、role4等角色把员工加入到各产品线
-
对于每个项目经理而言,至少有两个角色,一是用于表示它的职位(role1,项目经理),二是用于表示它所在的部门(role3、role4等)
-
所以在权限模型中可形成角色与组织架构的映射关系,为业务处理提供了数据支撑
-
-
A产品线的项目经理(role2)不能查看公司所有项目概览(资源为res1,但是否排除为true)
-
A产品线下的所有员工(role3)都可以访问产品A(res3)
Rest与统一资源定位
要受权限管理的资源有API、菜单、页面元素、数据记录等,如何统一管理?

我们在资源(Resource)模型中添加了统一定位(uri)字段,在权限(Permission)模型中添加了操作方法(operation)。
传统的方式可以为不同的资源类型建不同的表,但本文更倾向于使用统一资源定位(URI)来表示。格式如下:
<kind>://<appId>.<tenantId>/<path>?<property>=<property value>
API资源:
kind = api
path = API路径
菜单资源:
kind = menu
path = 菜单树节点Id
页面元素资源:
kind = element
path = 页面路径/元素Id
或 path = 页面路径?<属性名>=<属性值>
数据资源:
kind = data
path = 数据库类型/库名/表名/fields/字段名
或 path = 数据库类型/库名/表名/rows/{主键值}
或 path = 数据库类型/库名/表名/rows?<字段名>=<字段值>
例如:
// 租户T1下应用A1的API “/user/{userId}” 资源
api://a1.t1/user/{userId}
// 租户T1下的API “/user/{userId}” 资源
api://t1/user/{userId}
// …菜单 “用户管理(userMgr)/批量导入(batchImport)” 资源
menu://…/userMgr/batchImport
// … “用户管理页面(userMgr)的删除按钮(id = 'userDelete')” 资源
element://…/userMgr/userDelete
// … “用户管理页面(userMgr)的删除按钮(class = 'userDelete')” 资源
element://…/userMgr?class=userDelete
// … “mysql下xx库user表idcard字段” 资源
data://…/mysql/xx/user/fields/idcard
// … “mysql下xx库user表主键为100” 资源
data://…/mysql/xx/user/rows/100
// … “mysql下xx库user表身份证为331xxx” 资源
data://…/mysql/xx/user/rows?idcard=331xxx
有了URI后我们可以精确地定位需要的资源,接下就是操作(operation)了,这时我们发现可以引用Rest的方式实现对资源的精确操作。
回顾下HTTP的常见方法:
-
GET 获取资源
-
POST 非幂等创建或更新资源
-
PUT 幂等创建或更新资源
-
DELETE 删除资源
-
PACTH 非幂等(因为可能存在++操作)更新资源的字段
我们可以发现这种CRUD方法天然地支持我们对资源的各类操作。
Tip
|
HTTP是REST的一个主要表现协议,但REST本身是协议无关的,也更没有规定HTTP方法与资源操作的映射关系,所以严格意义上我们要明确两者的关系,但在实际项目及开发中可近似地认为REST就是用于HTTP的。 详见笔者的 《微服务架构设计》 |
应用鉴权
上文都是针对用户的权限控制,但有需求需要实现针对应用的权限控制,如应用A提供的API除了要限制用户的访问外还要限制应用的访问。

由于应用与用户不同,它不需要群组、角色定义,所以我们添加了应用权限(AppPermission)模型,让应用权限模型直接与资源及应用关联,实现一个简单的ACL模型。
小结
本文我们简单地介绍了用户权限中心的权限模型设计,与前文用户认证模型一样都是基于租户隔离策略,但现实情况下存在需要跨租户访问的场景,那么在用户认证和权限上我们又需要做什么处理呢?在下一篇文章中我们会就此展开讨论。