RBAC 模型详解:核心RBAC、层级RBAC、约束RBAC
RBAC 不是什么新鲜概念,但能把它的三种变体用透的人,说实话不多。我在不少项目里见过“伪 RBAC”——用户绑了一堆角色,角色绑了一堆权限,结果代码里到处都是 if-else 判断。那不是 RBAC,那是权限面条。
这一节我们就把 RBAC 的三个主力版本聊透:核心、层级、约束。你想想看,从最简单的“用户-角色-权限”到能支持几千人组织的继承体系,再到银行系统里那种“不能既审核又打款”的安全约束,这三层正好对应了从小团队到大型企业的真实演进路径。
3.1 核心RBAC(Core RBAC)——地基
核心 RBAC 定义了四个基本元素:用户(User)、角色(Role)、权限(Permission)、会话(Session)。说白了,就是用户登录时激活一组角色,角色授权一组操作。
我刚开始做权限时犯过一个错:把权限直接挂在用户上。后来一个 200 人的系统改了 30 多次权限表,表结构乱成一锅粥。嗯,从那以后我再也不干这种事了。
核心 RBAC 的要点用表格列一下:
| 元素 | 含义 | 举例 |
|---|---|---|
| 用户 | 人/系统主体 | 张三(用户ID=1001) |
| 角色 | 权限的集合 | 运营专员、审核主管 |
| 权限 | 对资源的具体操作 | 创建订单、查看报表、删除记录 |
| 会话 | 用户激活角色的一次登录 | 张三今天登录后激活了“运营专员”角色 |
这里有个容易被忽略的点:会话是动态的。用户可以有多个角色,但不一定全部激活。我记得在做一个 SaaS 租户系统时,用户既是“租户管理员”又是“普通成员”,如果同时激活两个身份会导致权限叠加出 bug。后来我强制会话只能激活一个角色组,问题就解决了。
is_default字段,用来标记用户登录后默认激活哪些角色。配合会话管理,可以干净地实现“最小权限激活”。
代码层面,核心 RBAC 的授权判断其实就三行逻辑:
// 伪代码:核心RBAC授权判断
function checkAccess(user, action, resource) {
roles = getUserActiveRoles(user); // 获取用户当前会话激活的角色
permission = findPermission(action, resource); // 查找需要的权限
return hasAnyRolePermission(roles, permission); // 看角色集里谁有这个权限
}
你看,没有硬编码,没有魔数。角色就是权限的容器,用户就是角色的容器。
3.2 层级RBAC(Hierarchical RBAC)——继承的力量
核心 RBAC 有一个硬伤:每个角色都要重复定义权限。比如“高级运营”和“初级运营”,前者多一个数据导出权限,后者没有。如果没有继承,你得把两个角色的权限列表全列一遍。
层级 RBAC 引入了角色继承。子角色自动拥有父角色的所有权限,并且可以增加自己的专有权限。这在组织架构里特别实用——一个部门经理自动拥有他手下所有助理的权限,再加上审批权。
我曾经在一个金融项目中踩过坑:用了多继承(一个角色从两个父角色继承),结果“审计员”同时继承了“管理员”和“分析师”,权限瞬间膨胀到不可控。后来我们规定单继承 + 显式拒绝(用 deny 覆盖 inherit)才控制住局面。
层级 RBAC 的关系图可以这样理解:
角色继承是方向性传递:如果“高级运营”继承“运营专员”,而“运营专员”继承“普通员工”,那么“高级运营”也拥有“普通员工”的所有权限。
实际建表时,可以存一张role_hierarchy表:
-- 角色继承表
CREATE TABLE role_hierarchy (
parent_role_id INT NOT NULL,
child_role_id INT NOT NULL,
PRIMARY KEY (parent_role_id, child_role_id)
);
-- 查询某个角色及其所有父角色的权限(递归,MySQL 8+)
WITH RECURSIVE cte AS (
SELECT role_id FROM roles WHERE role_id = ? -- 当前角色
UNION ALL
SELECT rh.parent_role_id FROM role_hierarchy rh
INNER JOIN cte ON rh.child_role_id = cte.role_id
)
SELECT DISTINCT p.* FROM permissions p
JOIN role_permissions rp ON p.permission_id = rp.permission_id
WHERE rp.role_id IN (SELECT role_id FROM cte);
嗯,递归查询在数据量大时要注意性能。我建议在业务层做角色树的缓存,而不是每次都跑递归。
3.3 约束RBAC(Constrained RBAC)——安全底线
约束 RBAC 是在层级 RBAC 的基础上加了控制规则,专门解决“权限滥用”问题。最常见的是两类约束:
- 职责分离(SoD):一个人不能同时拥有冲突的角色。比如不能同时是“采购员”和“验收员”,否则就能自己买自己验,容易腐败。
- 基数约束:限制某个角色最多被多少人拥有。比如“系统管理员”最多只能 2 个人。
我曾经在一个政务系统里处理过动态职责分离:用户可以在不同会话里扮演不同角色,但同一个会话中不能同时激活两个互斥角色。比如张三上午以“审批员”身份工作,下午换“审计员”角色登录,这是允许的;但如果在同一个会话里同时激活这两个角色,系统会抛异常。实现起来就是在会话激活角色时做一次互斥校验。
约束 RBAC 的配置通常像下面这样:
// 静态职责分离配置(SSD)
{
"ssd_policies": [
{ "conflict_roles": ["采购员", "验收员"], "threshold": 1 },
{ "conflict_roles": ["制单员", "审核员"], "threshold": 1 }
]
}
// 动态职责分离(DSD)可以在会话层做校验
function activateRole(user, session, roleId) {
const currentRoles = session.getActiveRoles();
const rules = getDsdRules(); // 获取动态互斥规则
for (let rule of rules) {
if (rule.conflict_roles.includes(roleId) &&
currentRoles.some(r => rule.conflict_roles.includes(r))) {
throw new Error('DSD violation: conflict roles');
}
}
session.addRole(roleId);
}
三种RBAC的对比
| 类型 | 核心能力 | 适用场景 | 复杂度 |
|---|---|---|---|
| 核心RBAC | 用户-角色-权限映射 | 小型系统、用户数<100 | 低 |
| 层级RBAC | 角色继承,权限复用 | 中大型组织、部门层级多 | 中 |
| 约束RBAC | 职责分离、基数控制 | 金融、政务等安全敏感场景 | 高 |
你想想看,如果团队只有 5 个人,用核心 RBAC 就够了;400 人的公司必须上层级 RBAC,否则权限维护成本会爆炸;涉及钱和敏感数据的系统,一定要把约束 RBAC 加上,不然审计过不了。
下面这张图把三者的关系做了可视化:
从图中能看出,这三种模型不是互斥的,而是递进关系。你可以从核心 RBAC 起步,随着组织规模和安全需求增加,逐步引入层级和约束。我见过很多团队一上来就上约束 RBAC,结果把简单的事情做复杂了——先跑通核心模型,再慢慢加继承和规则,才是务实路线。
好啦,关于 RBAC 的三种形态就说到这。下次你再听到“我们权限系统是 RBAC 的”,可以追问一句:“核心、层级,还是约束?” 答案不同,背后的设计差异可大着呢。