在《用户认证模型设计》、《权限模型设计》、《隔离与交互策略模型》中笔者介绍了用户权限的演进,逐步形成了相对完善的模型体系,但在文末也遗留了些改进思考。本文将延续之前的演进以改进点为切入聊一下更优雅的模型。
改进思考回顾及补充
-
资源(Resource)加上资源操作(Operation)用于定位一个操作,但这一模型下“资源”并不纯粹,比如有HTTP API“GET /user/{userId}”及“GET /mgr/user/{userId}”,两个API对应的角色不同,但它们对应的都是用户资源,目前的资源模型需要2条记录,怎么更优雅地实现?
-
账号认证配置(AccountIdentConfig)中配置了OAuth的AK/SK信息以存放微信公众号的AppId/AppSecret,这看似合理,但有没有更统一、简单的模型?
-
共享应用章节介绍的模型有个缺陷:无法实现不同应用的自定义授权。如商城应用和在线投保应用去申请消息中心应用的权限,给到的权限是对等的,无论实现消息中心模块1的权限给商城应用、模块2、3的权限给在线投保应用,但这种场景却又是存在的
-
本次涉及的权限模型包含了RBAC、ACL,在统一资源定位章节又涉及了部分ABAC,有没有简单、优雅的方案实现ABAC权限模型?
针对上述问题,演化出了统一资源模型
及简化版ABAC权限模型
。
统一资源模型
此模型将一切可操作的元素都视为“资源”,包含但不限于:API、菜单、页面元素、关系型数据库/表/行/字段、Redis缓存key、OAuth服务等。可以简单地将资源看作多树结构,每棵树的树根为资源主体(ResourceSubject),它包含了该树所有节点的公共信息,每个节点为资源(Resource)。
比如资源主体(ResourceSubject):
code | kind | name | uri | ak | sk | platform_account | rel_app_id | rel_tenant_id |
---|---|---|---|---|---|---|---|---|
1.api.iam |
API |
用户权限中心接口服务 |
1 |
1 |
||||
2.menu.iam |
MENU |
商城菜单 |
menu://iam |
2 |
2 |
|||
2.element.iam |
ELEMENT |
商城页面元素 |
menu://iam |
2 |
2 |
|||
2.reldb.mall |
RELDB |
商城数据库 |
mysql://192.168.0.100:3306/test |
admin |
pwdxxxx |
2 |
2 |
|
2.cache.mall |
CACHE |
商城缓存库 |
redis://localhost:6379/1 |
pwdxxxx |
2 |
2 |
||
2.mq.mall |
MQ |
商城MQ |
amqp://localhost:10000/vhost1 |
admin |
pwdxxxx |
2 |
2 |
|
2.oauth.mall |
OAUTH |
商城微信公众号 |
oauth://WECHAT-XCX |
wx1234567f72a79132 |
12345678912345678912345678912345 |
2 |
2 |
|
2.obj.mall |
OBJ |
商城对象存储库 |
ONDOWNFDSWXIDSS |
bSKpunPLyZyuLhbPRONsdnYH0q7dw2dfTl |
2 |
2 |
||
2.vod.mall |
VOD |
商城视频服务 |
ONDOWNFDSWXIDSS |
bSKpunPLyZyuLhbPRONsdnYH0q7dw2dfTl |
gudaoxuri |
2 |
2 |
如上所示,我们可以定义不同的资源主体类型(Kind),每个资源主体都有对应的URI、要访问该资源的通用认证信息(AK/SK)及特殊信息(比如像华为的视频点播服务需要使用平台账号名),同时每个资源主体也有对应的所属应用Id及租户Id。
资源主体的URI用于定位资源,一般可直接寻址找到资源,但也有需要二次处理的情况,比如MENU、ELEMENT、OAUTH这几个都应该路由到用户权限系统,由用户权限系统来理解URI并操作对应的资源。
资源主体编码可以由<所属应用Id>.<资源类型名称>.<自定义编码>
组成,如2.reldb.main
表示应用2的主数据库,2.reldb.read
表示应用2的只读库。
接下来我们看一下资源(Resource):
name | uri | action | expose_kind | rel_app_id | rel_tenant_id |
---|---|---|---|---|---|
系统控制台资源 |
api://1.api.iam/console/system/** |
APP |
1 |
1 |
|
应用控制台资源 |
api://1.api.iam/console/app/** |
GLOBAL |
1 |
1 |
|
商城批量导入菜单 |
menu://2.menu.iam/userMgr/batchImport |
/pages/user/import |
APP |
2 |
2 |
商城用户管理页面(userMgr)的删除按钮(id = 'userDelete') |
element://2.element.iam/userMgr/userDelete |
APP |
2 |
2 |
|
商城用户管理页面(userMgr)的删除按钮(class = 'userDelete') |
element://2.element.iam/userMgr?class=userDelete |
APP |
2 |
2 |
|
商城商品表 |
reldb://2.reldb.mall/item |
APP |
2 |
2 |
|
商城商品表商品名字段 |
reldb://2.reldb.mall/item/field/name |
APP |
2 |
2 |
|
商城商品表商品主键=100的记录 |
reldb://2.reldb.mall/item/row/100 |
APP |
2 |
2 |
|
商城商品表商品身份证=331xxx的记录 |
reldb://2.reldb.mall/item/row?idcard=331xxx |
APP |
2 |
2 |
资源的URI是对外暴露的资源地址,由<资源主体编码>+<资源路径组成>+<资源查询条件>
组成,资源路径支持ant表达式,资源查询条件可以精确到资源属性级与记录级的定位。
资源开放等级类型名称(expose_kind)用于确定该资源的作用域,默认是应用级(APP),即只有该应用(rel_app_id)可以使用,也可以是租户级(TENANT)表示只有该租户(rel_tenant_id)下的应用可以使用、全局(GLOBAL)表示所有应用都可以使用。如上示例中的应用控制台,从功能上看这个控制台要支持所有应用登录后操作自己应用相关的数据,所以这个资源由用户权限这个应用提供(rel_app_id = <用户权限中心的应用Id>),但它的开放等级为GLOBAL,这样所有的应用都可以访问到对应的API。
触发后的操作(action)一般用于点击某个菜单或元素后打开一个链接,该链接多为前端的路由地址。
通过上述两张简单的表我们可以很容易实现一个可扩展的、统一的资源模型。
简化版ABAC权限模型
以笔者的经验看主流的RBAC模型及其扩展模型在一般的互联网场景中可以比较好的支撑,但在传统企业、产业互联网企业中往往存在着复杂的组织架构关系及特殊的权限要求,仅以角色为出口关联用户与权限的方式就显得过于简陋了。所以我们会去考虑ABAC模型,后者有很多的好处,引用 AWS相关的介绍 如下:
相比传统 RBAC 模型,ABAC 具备以下优势: ABAC 权限随着创新扩展。 它不再需要管理员更新现有策略以允许对新资源的访问。例如,假设您使用 access-project 标签指定了 ABAC 策略。开发人员使用 access-project = Heart 标签的角色。当 Heart 项目中的员工需要额外的 Amazon EC2 资源时,开发人员可以使用 access-project = Heart 标签创建新 Amazon EC2 实例。这样,Heart 项目中的任何员工可以启动和停止这些实例,因为其标签值匹配。 ABAC 需要较少的策略。 由于您无需为不同工作职能创建不同策略,需要创建的策略数量减少。这些策略更易于管理。 使用 ABAC,团队可以进行更改和扩展。 这是因为新资源的权限根据属性自动授予。例如,如果您的公司已经使用 ABAC 支持 Heart 和 Sun 项目,则可以轻松地添加新 Lightning 项目。IAM 管理员创建具有 access-project = Lightning 标签的新角色。无需更改策略以支持新项目。有权代入该角色的任何用户可以创建和查看使用 access-project = Lightning 标记的实例。此外,团队成员可以从 Heart 项目转向 Lightning 项目。IAM 管理员将用户分配到不同 IAM 角色。无需更改权限策略。 使用 ABAC 可以实现精细权限。 在您创建策略时,最佳实践是授予最小权限。使用传统 RBAC,您必须编写一个策略,仅允许访问特定资源。但是,如果使用 ABAC,您可以允许在所有资源上的操作,但仅在资源标签与委托人标签匹配时。
ABAC有这么多优势,但它无法得到广泛地使用是因为其以决策导向,在性能上相对较弱,尤其是对于高频访问的操作而言,这个因素影响很大。
ABAC的热门实现可以参见 Casbin 项目。
笔者以ABAC为基础,设计了一个简化版本的模型:
rel_subject_kind | rel_subject_ids | subject_operator | rel_resource_id | action_kind | result_kind | rel_app_id | rel_tenant_id |
---|---|---|---|---|---|---|---|
ACCOUNT |
1000,10010, |
EQ |
5000 |
FETCH |
ACCEPT |
2 |
2 |
ROLE |
2000, |
NEQ |
5000 |
CREATE |
ACCEPT |
2 |
2 |
GROUP_NODE |
3000, |
INCLUDE |
5000 |
EXISTS |
ACCEPT |
2 |
2 |
GROUP_NODE |
2, |
LIKE |
5000 |
MODIFY |
ACCEPT |
2 |
2 |
TENANT |
2, |
EQ |
5000 |
DELETE |
REJECT |
2 |
2 |
权限决策模型的核心是将资源与各个对象关联起来,这种对象在该模型中称为权限主体,即针对谁做的权限。
权限主体有很多类型(rel_subject_kind),包含但不限于账号(ACCOUNT)、群组节点(GROUP_NODE)、角色(ROLE)、应用(APP)、租户(TENANT),一条权限可以指定同一权限主体类型的多条记录(rel_subject_ids),用逗号分隔。
权限决策可以选择到相应开放等级的资源(rel_resource_id),如果权限主体与资源同属于一个应用,那么自然可以关联,但如果是不同应用则要看对应的资源是否开放,如果资源1000在T1租户的A1应用下,而要权限主体在T2租户的A2应用下,那么只有当资源1000的开放等级类型(expose_kind)为GLOBAL时才能关联。
资源是可操作的对象但不包含操作这个动作本身,对于同一资源可以有很多的操作类型(action_kind),包含但不限于是否存在(EXISTS)、创建(CREATE)、修改(MODIFY)、获取(FETCH)、删除(DELETE)。
确定了权限主体及资源后还要明确他们的关系(subject_operator),支持但不限于等于(EQ)、不等于(NEQ)、包含(INCLUDE)、相似(LIKE),包含与相似多用于群组节点主体,由于群组节点的ID带上下级关系(见前文),所以通过包含与相似可以避免递归,方便地定位到祖父、子孙节点。
最后还要明确策略命中的处理方式(result_kind),常见于通过(ACCEPT)、拒绝(REJECT)。再复杂的可以扩展出修正(MODIFY)这一方式并给出修正逻辑,比如用户A命中了“商城订单表”资源的获取(FETCH)策略,需要自动在SQL语句中加上(create_by = ?,[A])以只显示用户A的订单,那么可以将result_kind设置为MODIFY,并加上result_process字段,内容为包含create_by = ?,[A]的可识别信息。
总结
最后附上整体模型架构,有兴趣的同学可以移步笔者新建的
dew-serviceless 项目(没错,不是serverless,项目构建中,正在逐步完善),上述完整的SQL脚本在module/iam/src/main/resources/sql/init.sql
中。