4. RBAC 进阶:角色继承、角色互斥、角色会话

做权限设计,光有基础的 RBAC(用户-角色-权限)够不够?

说实话,小公司还行。一旦上了规模,你会遇到很多棘手的问题。比如一个“市管理员”权限为啥要比“省管理员”小?一个用户能不能同时当“会计”和“出纳”?用户登录后的权限状态怎么管理?

这些问题,基础 RBAC 搞不定。我们得聊聊它的进阶版。

4.1 角色继承:让权限“长”出层级

什么叫角色继承?说白了,就是“儿子”拥有“老子”的所有权限,还能额外加点自己的。

我举个例子。你做一个企业内部系统。有“普通员工”、“部门经理”、“总监”。一般情况下,“总监”的权限应该包含“部门经理”的权限,“部门经理”的权限又包含“普通员工”的权限。

如果你用基础 RBAC,需要把“查看员工信息”、“审批请假”这些权限,在每一个角色里都重新关联一遍。

这太蠢了。 数据冗余大,维护成本高,还容易出错。

角色继承就是为了解决这个问题。它分两种:

  • 通用继承:子角色自动获得父角色的所有权限。这是最常见的。
  • 受限继承:子角色只能继承部分父角色权限,或者可以“覆盖”父角色的某些权限。
我的个人习惯:在设计初期,尽量使用通用继承。不要一上来就搞受限继承,很容易把权限模型搞乱。等系统跑起来,发现有特殊情况,再逐步收窄覆盖范围。

它的实际应用场景非常清楚:

业务场景 继承关系 举例
组织层级 上级角色继承下级角色 总监 > 经理 > 员工
项目角色 高级角色继承基础角色 Project Lead > Developer > Tester
区域管理 全局管理员继承区域管理员 全国管理员 > 大区管理员 > 城市管理员
我曾经踩过一个坑:在设计一个教育系统时,我让“班主任”角色直接继承了“任课老师”的所有权限。结果发现“班主任”看到了不应该看到的成绩排名细节。因为“班主任”不应该有“任课老师”的某些敏感统计数据权限。后来改成受限继承,才解决问题。所以,继承关系要“收着点”设计,不要贪图方便。

4.2 角色互斥:让权力不能“一手遮天”

你想想看,一个人既做采购,又做验货,是不是很危险?

这就是角色互斥要解决的。它不允许同一个用户同时拥有两个 互斥的 角色。

为什么要搞这个?

为了 职责分离(Separation of Duties)。这是安全审计的基本要求。它分两种实现方式:

  • 静态互斥(SSD):在用户分配角色时直接限制。你给张三分配了“会计”角色,就不能再给他分配“出纳”角色了。系统直接拒绝。
  • 动态互斥(DSD):用户可以被分配两个互斥角色,但不能在同一个会话中同时激活它们。比如你既是“会计”又是“出纳”,但你不能在同一个登录会话里同时使用两种权限。

我个人觉得,静态互斥更严格,也更安全。动态互斥更多是业务灵活性的妥协。下面这个表能帮你快速对比:

特性 静态互斥(SSD) 动态互斥(DSD)
限制时间点 角色分配时 角色激活时(会话)
灵活度 低 中
实现复杂度 低 中
适用场景 强安全合规 灵活业务流转
实际业务案例:
  • 金融系统:交易员不能同时是审核员。这是静态互斥。
  • 电商后台:商品上架员不能同时是商品审核员。这是静态互斥。
  • 实验环境:开发者不能同时在同一个会话里拥有“管理员”和“普通用户”角色。这是动态互斥。

4.3 角色会话:用户权限的“活动状态”

基础 RBAC 里,用户、角色、权限的关系是静态的。但用户登录后,他的权力是“活”的。这个“活”的状态,就是角色会话。

角色会话,记录了用户在 当前登录时间内,激活了哪些角色。

你想想看,一个用户可能有 10 个角色。但他一次只能干一件事。他没必要同时拥有 10 个角色的所有权限。

所以,会话的作用就是 限定 用户“当前”能干什么。

  • 单一会话:一个用户一次登录只能激活一个角色。切换角色需要重新登录。
  • 多角色会话:一个用户一次登录可以同时激活多个角色。用户拥有这些角色的权限的“并集”。

怎么在代码里实现呢?我讲一个最朴素的思路:

// 伪代码:角色会话管理
class RoleSession {
    constructor(userId) {
        this.userId = userId;
        this.activeRoles = []; // 当前会话已激活的角色列表
    }

    // 激活一个角色
    activateRole(role) {
        // 检查角色互斥(动态互斥)
        if (this.isExclusive(role)) {
            throw new Error("角色互斥,无法激活");
        }
        this.activeRoles.push(role);
    }

    // 检查角色是否互斥
    isExclusive(role) {
        // 遍历已激活的角色,检查是否有与当前角色互斥的
        for (let activeRole of this.activeRoles) {
            if (activeRole.mutualExclusiveRoles.includes(role.id)) {
                return true;
            }
        }
        return false;
    }

    // 获取当前用户拥有的所有权限(从已激活角色中汇总)
    getPermissions() {
        let permissions = [];
        for (let role of this.activeRoles) {
            for (let perm of role.permissions) {
                if (!permissions.includes(perm)) {
                    permissions.push(perm);
                }
            }
        }
        return permissions;
    }
}

这个会话设计非常重要。它让你能精细控制用户 同一时间 能做什么。很多安全漏洞,就是因为没有管理好会话中的角色。

请注意:在多角色会话中,一定要处理好权限的“覆盖”和“并集”问题。不要出现一个低权限角色“稀释”了高权限角色的权限,或者一个敏感操作被非预期角色触发。

4.4 三者的协作关系:一张图讲明白

你可能会问,这三个东西怎么一起用?它们不打架吗?

不会。它们是分工合作的。我用一张图来表示它们的关系:

RBAC进阶:角色继承、互斥与会话关系 RBAC进阶核心关系图 核心用户 角色继承(树) 角色互斥(约束) 角色会话(活跃状态) 权限(最终生效) 拥有 不允许同时拥有 在会话中激活 动态互斥 汇总出 协作规则 1. 角色继承决定“有什么” 2. 角色互斥决定“不能同时有” 3. 角色会话决定“当前有什么” 三者共同决定用户的实际权限

这张图的关键在于:

  1. 用户 首先通过 角色继承 获得所有可能的角色。
  2. 角色互斥 作为一道防火墙,阻止用户获得冲突的角色。
  3. 角色会话 是用户登录后,实际激活的角色集合。它受互斥约束。
  4. 最终,用户的 权限 是会话中所有激活角色权限的并集。

你有没发现,这个模型就像一个三明治:继承和互斥是静态层,会话运行态,权限是最终结果。它们各司其职,非常清楚。

嗯,到这里你应该能理解,为什么复杂的业务系统一定要用这些进阶特性了。它们不是理论玩具,是保障权限系统安全、灵活、可扩展的基石。