03-RBAC模型详细设计-角色继承约束与权限计算
RBAC 模型详细设计:角色、继承、约束与权限计算
当系统有数千名员工和数百项功能权限时,逐人授权既昂贵又难以审计。RBAC 把权限分配给角色,再把用户映射到角色,使权限管理贴近岗位职责。
RBAC 的价值不只是少建几条用户权限记录,更重要的是提供角色层级、职责分离、会话激活和可治理的授权关系。
1. RBAC0:基础模型
核心关系是:
User ↔ Role ↔ Permission
用户和角色多对多,角色和权限多对多。权限编码表达稳定能力,例如 finance:payment:approve;角色表达职责集合,例如 FINANCE_MANAGER。
基础表:
CREATE TABLE iam_role (
id BIGINT PRIMARY KEY,
tenant_id BIGINT NOT NULL,
role_code VARCHAR(64) NOT NULL,
role_name VARCHAR(128) NOT NULL,
role_type VARCHAR(32) NOT NULL,
risk_level VARCHAR(16) NOT NULL,
owner_id BIGINT NULL,
status VARCHAR(32) NOT NULL,
version BIGINT NOT NULL DEFAULT 1,
UNIQUE KEY uk_role_code (tenant_id, role_code)
);
CREATE TABLE iam_role_permission (
role_id BIGINT NOT NULL,
permission_id BIGINT NOT NULL,
valid_from DATETIME NULL,
valid_until DATETIME NULL,
PRIMARY KEY (role_id, permission_id)
);
普通 RBAC 主要表达正向权限,不建议大量引入负权限。需要禁止时,优先使用职责分离、Scope、ABAC 或不授予权限。
2. RBAC1:角色层级
专题统一方向是:高级角色继承基础角色权限。
FINANCE_MANAGER → FINANCE_SPECIALIST
这意味着财务主管获得财务专员的基础能力,不意味着财务专员获得主管审批权。
角色层级可以是树,也可以是 DAG。多继承有助于组合能力角色,例如 PROJECT_FINANCE_MANAGER 同时继承项目读取和财务审批角色,但要检测循环和限制深度。
作用域不会因继承扩大
王芳获得 FINANCE_MANAGER,Scope 为华东分公司。即使该角色继承 FINANCE_SPECIALIST,继承来的创建、读取权限仍受华东分公司限制。角色继承传播权限集合,不传播一个更大的组织范围。
如何停止继承
RBAC 不适合像 ACL 一样在某个用户上设置“停止继承”。更好的方式是:
- 删除角色层级关系;
- 拆分为多个能力角色;
- 组合所需角色;
- 把高风险能力放在不可继承的独立角色中;
- 用 ABAC 限制使用条件。
通过“排除继承权限”建立复杂负权限会让角色很难解释,应谨慎使用。
3. RBAC2:约束
静态职责分离 SSD
用户不能同时被授予互斥角色,例如租户权限管理员和审计员、付款申请人和最终复核人。系统在 Role Assignment 创建时拒绝冲突。
动态职责分离 DSD
用户可以拥有多个角色,但不能在同一会话或同一笔业务中同时使用。例如刘敏可能临时拥有审批能力,却仍不能审批本人创建的 P1001。
基数约束
高风险角色最多允许若干成员,或一个会话最多激活若干角色。这用于限制超级管理员和资金审批角色扩散。
前置角色
获得高额付款审批角色之前,要求已经拥有基础审批角色并完成安全培训。实际前置条件可能是角色,也可能是资质、MFA 和培训状态,因此复杂条件应交给 ABAC 或治理流程。
4. RBAC3
RBAC3 是角色层级与约束的组合。企业系统实际落地通常是 RBAC3 的思想加上 Scope、Data Scope、临时授权和 ABAC,而不是只实现五张表。
5. 用户组、组织、岗位和角色
四者必须分开:
| 对象 | 含义 | 典型用途 |
|---|---|---|
| 用户组 | 主体集合 | 项目组、应急组、跨部门协作 |
| 组织 | 管理与数据归属 | 部门树、审批关系、数据范围 |
| 岗位 | 组织中的职责位置 | 默认角色、入职转岗自动化 |
| 角色 | 权限集合 | 功能权限管理 |
“华东财务主管”既像岗位又像角色,建模时应拆成:岗位 POS-004@ORG-120,角色 FINANCE_MANAGER,Scope ORG-100。否则地区、部门和职位相乘会形成大量角色。
6. 企业级 Role Assignment
基础教学常用 user_role(user_id, role_id),但它无法表达来源、作用域、有效期和非人主体。
企业模型建议:
CREATE TABLE iam_role_assignment (
id BIGINT PRIMARY KEY,
tenant_id BIGINT NOT NULL,
subject_type VARCHAR(32) NOT NULL,
subject_id BIGINT NOT NULL,
role_id BIGINT NOT NULL,
scope_type VARCHAR(32) NOT NULL,
scope_id BIGINT NULL,
source_type VARCHAR(32) NOT NULL,
source_id BIGINT NULL,
granted_by BIGINT NULL,
valid_from DATETIME NULL,
valid_until DATETIME NULL,
status VARCHAR(32) NOT NULL,
version BIGINT NOT NULL DEFAULT 1,
KEY idx_subject (tenant_id, subject_type, subject_id, status),
KEY idx_role (tenant_id, role_id, status)
);
subject_type 可以是 Principal、Group、Organization 或 Position。运行时通过用户的成员关系获得间接角色,但不能把间接角色永久复制成用户直接角色,否则退出组织或用户组后难以回收。
同一角色可能有多个来源,必须分别保留。例如张三通过岗位和临时审批都获得同一角色,临时授权到期后不能误删岗位来源。
7. 会话角色激活
用户拥有角色不代表每个会话都要激活全部高权限。审计员兼任应急运维时,可以在普通会话只激活审计角色,需要运维时经过 MFA 和审批激活临时角色。
会话激活有助于:
- 减少长期暴露的高权限;
- 实现动态职责分离;
- 区分用户“拥有”与“正在使用”的角色;
- 对高风险角色实施短时提权。
8. 多角色权限合并
功能权限默认取并集,但权限和 Scope 必须成对计算:
sales:order:read → SELF
sales:order:read → DEPARTMENT
sales:order:export → PROJECT-A
同一权限的多个允许 Scope 通常取并集;不同 Action 不能共享范围。读取华东订单的范围不能自动成为导出华东订单的范围。
高风险权限可以要求指定授权来源、使用更小作用域或不允许普通并集合并。
9. 角色爆炸
以下建模会迅速失控:
华东销售经理
华南销售经理
华东销售经理-可导出
华南销售经理-不可导出
……
解决方式:
- 角色只表达功能能力;
- 地区和部门放入 Scope/Data Scope;
- 动态条件放入 ABAC;
- 项目成员使用用户组或 ReBAC;
- 用角色模板创建租户角色,但仍保留独立实例和负责人;
- 定期清理重复、无人使用和无人负责的角色。
10. 非人主体的 RBAC
服务账号、微服务和 Agent 可以使用 Role Assignment,但不要直接复用员工角色。
例如 order-service 获得 SVC_ORDER_INTEGRATION,仅包含读取客户基础信息和提交付款请求;它不应获得 FINANCE_MANAGER,即使其中部分权限相同。服务角色的所有人、环境、凭证和复核周期不同于员工角色。
11. 权限计算流程
- 查询主体直接 Role Assignment。
- 查询有效用户组、组织和岗位产生的间接角色。
- 计算角色继承闭包。
- 过滤过期、撤销和当前会话未激活角色。
- 将每条权限与产生它的 Scope、来源绑定。
- 执行互斥、基数和动态职责分离。
- 输出有效 RBAC 权限,交给 ACL、ABAC 和数据权限继续判断。
缓存可以分别保存角色权限、角色继承闭包和主体有效角色,但缓存不是事实源。角色或成员关系变化时,应增加主体权限版本并失效相关缓存。
12. Spring Security 落地边界
@PreAuthorize("hasAuthority('finance:payment:approve')")
@PostMapping("/payments/{id}/approval")
public void approve(@PathVariable long id,
@RequestBody ApprovalRequest request) {
paymentApplicationService.approve(id, request);
}
注解适合功能权限,不能单独完成数据范围、金额额度和自审批限制。Service 中仍要调用统一授权服务,并在 Repository 或最终写 SQL 中落实数据权限。
不要根据角色名称写业务逻辑:
// 不推荐
if (user.hasRole("FINANCE_MANAGER")) {
payment.approve();
}
业务代码应判断稳定的权限与策略,而不是假设某个角色永远包含固定含义。
13. 常见错误
- 组织、岗位、用户组和角色使用同一张表或同一概念。
- 职位高就自动继承全部下级能力。
- Scope 被编码进角色名,造成角色爆炸。
- 多角色只合并权限字符串,丢失每个权限的作用域。
- 使用大量负权限,最终无法解释冲突。
- 转岗只授予新角色,不撤销旧来源。
- 租户管理员被当作全部业务数据超级用户。
- 角色删除或权限撤销后,Token 和缓存仍长期有效。
14. 适用边界
RBAC 最适合功能权限和稳定职责,不适合单独表达具体文档分享、金额与风险条件、复杂项目关系。企业系统应把 RBAC 当作基础骨架,而不是唯一模型。
评论