3、RBAC进阶模型:RBAC1角色继承、RBAC2职责分离(SSD/DSD)、RBAC3统一模型

好,咱们继续往下走。上一章我们把RBAC0这个基础模型聊透了,说白了就是「用户-角色-权限」的铁三角。但实际落地的时候你会发现,光有基础模型根本不够用。

举个例子:你公司有「销售总监」和「销售专员」两个角色。专员能看自己的客户,总监能看全团队的客户。如果按RBAC0来做,你得给这两个角色分别配权限。但仔细想想,总监的权限其实完全覆盖了专员的权限,对吧?

这就是RBAC1要解决的问题——角色继承。

3.1 RBAC1:角色继承模型

角色继承,说白了就是「上级角色拥有下级角色的所有权限」。这在现实世界里太常见了:经理能做的,总监肯定能做;总监能做的,VP肯定能做。

我早期做第一个SaaS项目时,就踩过这个坑。当时给每个层级都单独配权限,结果客户公司有7个管理层级,配了整整两周,还经常出错。后来改用角色继承,一天就搞定了。

核心概念:

  • 父角色:拥有更广泛的权限集合
  • 子角色:继承父角色的所有权限,并可额外拥有自己的权限
  • 继承关系:通常是偏序关系,支持多级继承

重要原则:角色继承是单向的。子角色继承父角色,但父角色不能反过来拥有子角色的权限。这就像你爸能管你,但你不能管你爸——嗯,道理是一样的。

代码示例:角色继承的数据结构

// 角色继承关系定义
{
  "roles": [
    {
      "id": "role_admin",
      "name": "系统管理员",
      "parent_id": null,  // 顶级角色
      "permissions": ["system.config", "user.manage"]
    },
    {
      "id": "role_manager",
      "name": "部门经理",
      "parent_id": "role_admin",  // 继承管理员权限
      "permissions": ["report.view", "team.manage"]
    },
    {
      "id": "role_staff",
      "name": "普通员工",
      "parent_id": "role_manager",  // 继承经理权限
      "permissions": ["task.create", "task.view"]
    }
  ]
}

// 获取用户最终权限(递归合并)
function getEffectivePermissions(userRole) {
  let permissions = new Set();
  let current = userRole;
  
  while (current) {
    current.permissions.forEach(p => permissions.add(p));
    current = findParent(current.parent_id);
  }
  
  return Array.from(permissions);
}

你想想看,这种继承关系一旦建立起来,权限管理就变得特别优雅。你只需要维护每个角色「额外」的权限,公共权限全部由继承搞定。

我的经验:角色继承层级建议控制在3-5层。太深了容易混乱,而且性能会受影响。我曾经见过一个客户搞了8层继承,结果查一次权限要递归8次,页面加载慢得像蜗牛。

3.2 RBAC2:职责分离模型

RBAC1解决了权限复用的问题,但带来了一个新问题:权限过度集中。

举个例子:一个员工既可以是「采购员」又可以是「验收员」。那他自己采购、自己验收,岂不是想怎么搞就怎么搞?这在金融、医疗等合规要求高的行业,是绝对不允许的。

RBAC2就是专门解决这个问题的。它分两种:

3.2.1 静态职责分离(SSD)

SSD的意思是:一个用户不能同时拥有两个互斥的角色。这是在用户分配角色时就做检查的。

典型场景:

  • 「采购员」和「验收员」不能是同一个人
  • 「出纳」和「会计」不能是同一个人
  • 「审批人」和「申请人」不能是同一个人

代码示例:SSD约束检查

// 定义互斥角色对
const SSD_RULES = [
  { roleA: "purchaser", roleB: "inspector" },
  { roleA: "cashier", roleB: "accountant" },
  { roleA: "approver", roleB: "applicant" }
];

function assignRoleToUser(userId, newRole) {
  // 获取用户已有角色
  const userRoles = getUserRoles(userId);
  
  // 检查互斥
  for (let rule of SSD_RULES) {
    if (newRole === rule.roleA && userRoles.includes(rule.roleB)) {
      throw new Error(`用户已有${rule.roleB}角色,不能同时分配${rule.roleA}`);
    }
    if (newRole === rule.roleB && userRoles.includes(rule.roleA)) {
      throw new Error(`用户已有${rule.roleA}角色,不能同时分配${rule.roleB}`);
    }
  }
  
  // 通过检查,分配角色
  userRoles.push(newRole);
  saveUserRoles(userId, userRoles);
}

3.2.2 动态职责分离(DSD)

DSD比SSD更灵活一些。它允许用户拥有互斥角色,但在同一个会话或同一个操作流程中不能同时使用。

举个例子:一个员工可以同时是「采购员」和「验收员」,但他不能在同一笔采购单上既采购又验收。换句话说,他可以在A单上做采购,在B单上做验收,但不能A单上又采购又验收。

注意:DSD的实现比SSD复杂得多。你需要记录操作上下文(比如订单ID、流程实例ID),在运行时动态检查。我曾经在一个ERP项目中实现DSD,光状态管理就写了2000多行代码。

SSD vs DSD 对比:

维度 SSD(静态) DSD(动态)
检查时机 角色分配时 操作执行时
灵活性 低,严格互斥 高,上下文相关
实现复杂度 简单 复杂
适用场景 合规要求严格的行业 需要灵活性的企业

3.3 RBAC3:统一模型

RBAC3就是把RBAC1和RBAC2合并起来。它既支持角色继承,又支持职责分离。

你可能会问:这两个东西合在一起会不会冲突?

会的。举个例子:如果「经理」继承了「员工」的角色,而「员工」和「审批人」是互斥的,那「经理」能不能同时拥有「审批人」角色?

这就要看你的继承策略了。我个人习惯的做法是:

  1. 继承优先:子角色自动继承父角色的所有权限,包括互斥约束
  2. 约束覆盖:如果子角色自身有互斥约束,以子角色为准
  3. 传递性检查:检查继承链上所有角色的互斥关系

代码示例:RBAC3的权限检查

function checkRBAC3Permission(userId, operation, context) {
  // 1. 获取用户所有角色(包括继承来的)
  const allRoles = getAllRolesWithInheritance(userId);
  
  // 2. 检查SSD约束
  if (hasSSDConflict(allRoles)) {
    return { allowed: false, reason: "角色互斥冲突" };
  }
  
  // 3. 检查DSD约束(如果有上下文)
  if (context && hasDSDConflict(allRoles, operation, context)) {
    return { allowed: false, reason: "动态职责分离限制" };
  }
  
  // 4. 合并所有角色的权限
  const effectivePermissions = mergePermissions(allRoles);
  
  // 5. 检查具体操作权限
  return {
    allowed: effectivePermissions.includes(operation),
    roles: allRoles.map(r => r.name)
  };
}

核心要点:RBAC3不是简单的1+2,而是要考虑继承和约束的相互作用。我建议在实现时,先画一张角色继承树,然后在树上标注互斥关系,这样一目了然。

3.4 实际项目中的选择建议

说了这么多,你可能会问:我到底该用哪个?

我的建议是这样的:

  • 小团队(10人以下):RBAC0就够了,别搞复杂了
  • 中型企业(10-100人):RBAC0 + 部分RBAC1(角色继承),职责分离用手工控制
  • 大型企业(100人以上):RBAC3全上,但建议分阶段实施
  • 金融、医疗等合规行业:直接上RBAC3,SSD和DSD都要实现

避坑指南:我曾经帮一个客户从RBAC0迁移到RBAC3,花了整整3个月。最大的教训是——不要一次性把所有约束都加上。先跑通继承,再加SSD,最后加DSD。每一步都要充分测试。

好了,这一章的内容就到这里。RBAC1、RBAC2、RBAC3这三个模型,说白了就是给基础模型加了两层「保险」:继承让权限管理更高效,职责分离让权限控制更安全。下一章我们聊聊怎么把这些模型落地到数据库设计中,那才是真正考验架构能力的地方。