RBAC的层次结构:从基础到完备
RBAC这套模型,说起来其实是一层一层往上堆的。我在项目里见过不少团队,上来就想用最复杂的RBAC3,结果搞得一团糟。我个人的建议是——先搞懂每一层解决了什么问题,再决定用哪一层。
标准RBAC模型分四个层次,它们的关系我画了张图,先给你个全貌。
RBAC0:一切的基础
说白了,RBAC0就是“用户-角色-权限”的铁三角。用户不能直接拥有权限,只能通过角色来获得。这解决了最原始的问题——不再给每个人单独授权,而是把权限打包成角色。
RBAC0的正式定义包含三个核心元素:
- 用户(User)——系统的自然人主体
- 角色(Role)——权限的集合,作为用户与权限之间的中介
- 权限(Permission)——对某个资源的操作许可(例如“读订单”、“写商品”)
再加上两个关键关系:用户-角色分配(UA)和角色-权限分配(PA)。一个用户可以被赋予多个角色,一个角色也可以包含多个用户。权限同理。
你想想看,这种多对多的关系,在实际数据库里怎么存?
-- 典型RBAC0数据表设计(简化)
CREATE TABLE users (id INT PK, username VARCHAR);
CREATE TABLE roles (id INT PK, role_name VARCHAR);
CREATE TABLE permissions (id INT PK, resource VARCHAR, operation VARCHAR);
-- 关系表
CREATE TABLE user_roles (user_id INT, role_id INT, PRIMARY KEY(user_id, role_id));
CREATE TABLE role_permissions (role_id INT, permission_id INT, PRIMARY KEY(role_id, permission_id));
我见过很多初级工程师直接给用户表加一个 role_id 字段——那是大忌,因为用户和角色是多对多,不是一对多。嗯,这里要注意。
RBAC1:角色继承
现实世界中的职务天生就有等级。总经理能做的,部门经理大部分也能做,但反过来不行。RBAC1就引入了角色层次(Role Hierarchy),子角色自动继承父角色的所有权限。
继承规则:如果角色R1是R2的父角色,则R2自动拥有R1的所有权限。用户被赋予R2时,同时也拥有R1的权限。
举个例子:
| 角色 | 父角色 | 拥有的权限(含继承) |
|---|---|---|
| 超级管理员 | — | 全部权限 |
| 系统管理员 | 超级管理员 | 全部权限 |
| 安全审计员 | — | 日志查看、报表导出 |
| 普通运维 | 系统管理员 | 全部权限(但通过角色分配可做限制) |
RBAC2:约束
光有继承还不够——有些场景下你需要限制角色之间的“兼容性”。比如,出纳和会计不能是同一个人,否则就有舞弊风险。RBAC2提出了三类核心约束。
- 互斥角色(Mutually Exclusive Roles):一个用户不能同时拥有两个互斥角色。上面说的出纳与会计,就是经典例子。
- 基数约束(Cardinality Constraints):限制了某个角色下最多有多少用户。比如“总管理员”角色最多只能有2个人。
- 先决条件角色(Prerequisite Roles):用户必须先拥有角色A,才能被赋予角色B。比如必须先当“实习生”才能成为“正式员工”。
这三种约束在实际开发中,通常通过业务代码或配置表来控制,而不是直接写在数据库约束里。我的做法是:在角色分配接口里统一校验。
// 伪代码:角色分配时的互斥检查
function assignRole(userId, newRoleId) {
var userRoles = getRolesOfUser(userId);
var exclusiveRoles = getExclusiveRoles(newRoleId);
// 检查新角色与已有角色是否互斥
for each role in userRoles {
if (exclusiveRoles.contains(role)) {
throw new ConstraintViolation("角色互斥冲突");
}
}
// 基数检查
var maxUsers = getMaxUserCount(newRoleId);
var currentUsers = getCurrentUserCount(newRoleId);
if (currentUsers >= maxUsers) {
throw new ConstraintViolation("角色已满员");
}
// 先决条件检查
var prerequisites = getPrerequisiteRoles(newRoleId);
for each prereq in prerequisites {
if (!userRoles.contains(prereq)) {
throw new ConstraintViolation("缺少前置角色");
}
}
// 通过所有检查,执行分配
doAssignRole(userId, newRoleId);
}
RBAC3:层次 + 约束
这就是最完整的RBAC模型了——它把角色继承(RBAC1)和约束(RBAC2)同时合并进来。听起来强大,实际应用中却要格外小心。
为什么?因为继承和约束可能产生冲突。举个例子:
- 角色A和角色B是互斥的,不能赋予同一个人。
- 但角色C继承了角色B,而角色C本身与角色A并不互斥。
- 那么问题来了——用户被赋予角色C,通过继承又拥有了角色B的权限,这算不算绕过了互斥约束?
在标准RBAC3的定义中,约束检查是针对直接分配的角色,不阻止继承带来的“间接拥有”。但实际业务中,如果你是安全审计场景,这种间接拥有可能构成风险。我建议在项目里自己加一道逻辑:如果两个角色互斥,它们的子角色也自动互斥。这样做虽然比标准模型严格,但更安全。
| 层次 | 特性 | 适用场景 |
|---|---|---|
| RBAC0 | 最基础的用户-角色-权限 | 简单应用,权限数少于50 |
| RBAC1 | + 角色继承 | 有组织层级的系统(OA、ERP) |
| RBAC2 | + 约束(互斥、基数、先决条件) | 金融、合规场景 |
| RBAC3 | 继承 + 约束 | 大型组织,安全要求高,但复杂度也高 |
最后说一句心里话——RBAC3虽好,但别一上来就全量铺开。我见过太多团队把模型设计得无比华丽,结果维护成本飙升,最后又退回RBAC0加一些定制代码。还是那句话:从简单开始,按需升级。你有这个意识,就已经超越了80%的所谓“架构师”。