4. RBAC 角色约束模型(RBAC2):职责分离与基数约束
RBAC2 在核心 RBAC0 的基础上引入了约束机制,确保权限分配不会违反组织的安全策略或业务流程。约束是 RBAC 模型中最具实战价值的部分,它直接决定了权限系统能否在真实企业环境中落地。
核心认知: 没有约束的 RBAC 只是角色-权限的简单映射,有了约束才成为可治理的企业级权限体系。RBAC2 定义了四种约束类型:互斥角色(静态职责分离)、基数约束、先决条件角色、动态职责分离。
4.1 静态职责分离(SSD)与互斥角色
静态职责分离(Static Separation of Duty, SSD)在用户-角色分配阶段实施约束,防止同一用户同时拥有两个或多个互斥角色。这是最常见的约束类型,用于防范利益冲突或权限过度集中。
4.1.1 互斥角色的定义与数据表设计
互斥角色是指不能同时分配给同一用户的角色集合。例如:出纳员与会计审核员、采购员与验收员、系统管理员与安全审计员。
| 约束类型 | 检查时机 | 违反后果 | 典型场景 |
|---|---|---|---|
| 静态互斥(SSD) | 用户-角色分配时 | 分配被拒绝 | 出纳与会计审核 |
| 动态互斥(DSD) | 会话-角色激活时 | 激活被拒绝 | 同一人不同时段切换角色 |
| 基数约束 | 分配或激活时 | 超过上限被拒绝 | 一个部门只能有 1 个经理 |
| 先决条件角色 | 分配时 | 缺少前置角色被拒绝 | 必须先有“员工”才能有“经理” |
互斥角色的数据表设计需要在关系层面表达约束关系:
-- 互斥角色组表:定义一组互斥的角色
CREATE TABLE ssd_role_set (
set_id INT PRIMARY KEY,
set_name VARCHAR(100) NOT NULL,
description VARCHAR(255),
cardinality INT NOT NULL DEFAULT 1 -- 该组中最多允许分配的角色数
);
-- 互斥角色组成员表:关联角色到互斥组
CREATE TABLE ssd_role_set_member (
set_id INT NOT NULL,
role_id INT NOT NULL,
PRIMARY KEY (set_id, role_id),
FOREIGN KEY (set_id) REFERENCES ssd_role_set(set_id),
FOREIGN KEY (role_id) REFERENCES role(role_id)
);
-- 检查逻辑示例(PL/pgSQL):
-- 当给用户分配角色时,检查该角色所属的所有互斥组,
-- 如果用户已拥有该组中 cardinality 个角色,则拒绝分配。
实战要点: cardinality 字段控制互斥的严格程度。cardinality=1 表示完全互斥(只能选其一);cardinality=2 表示最多可选两个;cardinality=N 表示最多可选 N 个。这种设计比简单的二元互斥更灵活。
4.1.2 SSD 约束的验证流程
SSD 约束在用户-角色分配操作中实时验证,流程如下:
- 接收请求: 用户 U 请求分配角色 R
- 查询互斥组: 查找角色 R 所属的所有 SSD 互斥组
- 遍历检查: 对每个互斥组,统计用户 U 已分配的角色中属于该组的数量
- 阈值判断: 如果已分配数量 ≥ 该组的 cardinality,则拒绝分配
- 通过: 所有互斥组检查通过,允许分配
4.2 动态职责分离(DSD)
动态职责分离(Dynamic Separation of Duty, DSD)在会话-角色激活阶段实施约束。用户可以被分配多个互斥角色,但在同一个会话中不能同时激活它们。DSD 比 SSD 更灵活,适用于需要同一人不同时段扮演不同角色的场景。
4.2.1 DSD 与 SSD 的核心区别
| 对比维度 | SSD(静态) | DSD(动态) |
|---|---|---|
| 检查时机 | 用户-角色分配时 | 会话-角色激活时 |
| 用户能否拥有互斥角色 | 不能 | 可以 |
| 同一会话能否激活互斥角色 | 不适用 | 不能 |
| 典型场景 | 出纳 vs 会计审核 | 值班经理 vs 普通操作员 |
| 实现复杂度 | 低(分配时一次检查) | 高(每次激活时检查) |
4.2.2 DSD 数据表设计
-- 动态互斥角色组表
CREATE TABLE dsd_role_set (
set_id INT PRIMARY KEY,
set_name VARCHAR(100) NOT NULL,
cardinality INT NOT NULL DEFAULT 1 -- 同一会话中最多可激活的角色数
);
-- 动态互斥组成员表
CREATE TABLE dsd_role_set_member (
set_id INT NOT NULL,
role_id INT NOT NULL,
PRIMARY KEY (set_id, role_id),
FOREIGN KEY (set_id) REFERENCES dsd_role_set(set_id),
FOREIGN KEY (role_id) REFERENCES role(role_id)
);
-- 会话激活角色表(用于运行时检查)
CREATE TABLE session_active_role (
session_id INT NOT NULL,
role_id INT NOT NULL,
activated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (session_id, role_id),
FOREIGN KEY (session_id) REFERENCES session(session_id),
FOREIGN KEY (role_id) REFERENCES role(role_id)
);
性能注意: DSD 检查发生在每次角色激活时,高频场景下需要缓存互斥关系。建议将 DSD 规则加载到 Redis 等缓存中,避免每次激活都查询数据库。
4.3 基数约束
基数约束限制某个角色可以被分配给的用户数量上限,或者某个用户可以被分配的角色数量上限。基数约束用于控制权限的集中度,防止某个角色被过度授权。
4.3.1 基数约束的两种类型
- 角色基数: 一个角色最多能分配给多少个用户。例如:“总经理”角色只能分配给 1 个用户。
- 用户基数: 一个用户最多能拥有多少个角色。例如:普通员工最多分配 5 个角色。
4.3.2 基数约束数据表设计
-- 在角色表中增加基数字段
ALTER TABLE role ADD COLUMN max_users INT DEFAULT NULL COMMENT '角色最大分配用户数,NULL表示不限制';
-- 在用户-角色关联表中增加基数检查触发器
CREATE OR REPLACE FUNCTION check_role_cardinality()
RETURNS TRIGGER AS $$
DECLARE
current_count INT;
max_allowed INT;
BEGIN
-- 获取角色最大用户数
SELECT max_users INTO max_allowed FROM role WHERE role_id = NEW.role_id;
IF max_allowed IS NOT NULL THEN
-- 统计当前已分配用户数
SELECT COUNT(*) INTO current_count
FROM user_role
WHERE role_id = NEW.role_id;
IF current_count >= max_allowed THEN
RAISE EXCEPTION '角色 % 已达到最大用户数限制 %', NEW.role_id, max_allowed;
END IF;
END IF;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER trg_check_role_cardinality
BEFORE INSERT ON user_role
FOR EACH ROW
EXECUTE FUNCTION check_role_cardinality();
4.4 先决条件角色
先决条件角色约束要求用户在获得某个角色之前,必须先拥有另一个角色。这种约束用于建立角色的层级依赖关系,确保权限的渐进式授予。
4.4.1 先决条件角色的数据表设计
-- 先决条件角色关系表
CREATE TABLE role_prerequisite (
role_id INT NOT NULL, -- 目标角色
prerequisite_id INT NOT NULL, -- 前置角色
PRIMARY KEY (role_id, prerequisite_id),
FOREIGN KEY (role_id) REFERENCES role(role_id),
FOREIGN KEY (prerequisite_id) REFERENCES role(role_id)
);
-- 检查逻辑:分配角色 R 时,用户必须已拥有 R 的所有前置角色
-- 示例:分配“高级工程师”前,用户必须已有“工程师”角色
实战案例: 某企业的角色晋升路径为:实习生 → 初级工程师 → 工程师 → 高级工程师 → 架构师。每个高级角色都以前一个角色为先决条件。这种设计天然防止了“跳级授权”的安全风险。
4.5 四种约束的综合架构
在实际系统中,四种约束需要协同工作。下图展示了约束在权限管理全生命周期中的检查位置:
4.6 约束设计的实战建议
最佳实践:
- 优先使用 SSD: 静态约束在分配时检查,性能开销小,逻辑简单。能用 SSD 解决的场景不要用 DSD。
- DSD 用于特殊场景: 只有需要同一人不同时段扮演互斥角色时才使用 DSD,例如值班制度。
- 基数约束要留余量: 设置基数上限时考虑未来业务扩展,避免频繁修改。
- 先决条件避免循环: 设计先决条件关系时,必须做环检测,防止 A→B→A 的死循环。
- 约束组合使用: 一个角色可以同时受多种约束限制,例如“财务经理”既有基数约束(最多 1 人),又有 SSD 约束(不能同时拥有“出纳”角色)。
常见陷阱: 不要在设计阶段过度约束。约束越多,系统的灵活性越低,维护成本越高。建议从核心安全需求出发,逐步增加约束,而不是一开始就设计一个“完美”的约束体系。