4、RBAC2 权限系统详细设计与实现指南(含约束与审计)

【投稿赢 iPhone 17】「我的第一个开源项目」故事征集:用代码换C位出道! 10w+人浏览 1.6k人参与

目录

  1. RBAC2 模型核心概念
  2. 数据库表结构设计(含约束与审计)
  3. 工作流程详解
  4. Java 实现示例(含详细中文注释)
  5. API 使用示例
  6. 约束规则实现
  7. 审计功能增强
  8. 测试用例验证

一、RBAC2 模型核心概念

1.1 什么是 RBAC2?

RBAC2(Role-Based Access Control Level 2)在 RBAC1 的基础上增加了约束机制,用于定义角色分配和权限使用的业务规则,主要包括:

  • 角色互斥(Mutual Exclusion):某些角色不能同时分配给同一个用户
  • 基数约束(Cardinality Constraints):限制角色的用户数量或用户的角色数量
  • 先决条件约束(Prerequisite Constraints):分配某角色前必须先拥有其他角色
  • 运行时约束(Runtime Constraints):动态权限检查(如时间、IP等)

1.2 核心约束类型详解

1.2.1 角色互斥约束
  • 静态互斥:用户不能同时拥有互斥角色(如会计和出纳)
  • 动态互斥:用户在同一会话中不能同时激活互斥角色
1.2.2 基数约束
  • 用户基数:限制单个用户可拥有的角色数量
  • 角色基数:限制单个角色可分配的用户数量
1.2.3 先决条件约束
  • 分配高级角色前必须先拥有基础角色
  • 例如:分配"总监"角色前必须先拥有"经理"角色

1.3 形象类比

银行内部管控

  • 互斥约束:会计角色和出纳角色互斥,同一人不能既管账又管钱
  • 基数约束:系统管理员角色最多只能分配给3个人
  • 先决条件:要成为高级审计员,必须先担任普通审计员至少1年

这些约束确保了职责分离和风险控制,符合金融行业的合规要求

1.4 RBAC 模型演进对比

特性RBAC0RBAC1RBAC2
基础关系用户-角色-权限+角色继承+约束机制
灵活性
复杂度简单中等复杂
适用场景小型应用中型企业大型企业/金融/政府

二、数据库表结构设计(含约束与审计)

2.1 表结构总览

表名说明关系
sys_user用户表-
sys_role角色表-
sys_role_hierarchy角色继承关系表父角色 ↔ 子角色
sys_permission权限表-
sys_user_role用户-角色关联表用户 ↔ 角色
sys_role_permission角色-权限关联表角色 ↔ 权限
sys_role_constraint角色约束表定义各种约束规则
sys_constraint_violation_log约束违规日志表记录约束违规尝试
sys_audit_log审计日志表记录所有权限变更

2.2 新增约束相关表结构

2.2.1 角色约束表 (sys_role_constraint) - 核心新增
CREATE TABLE `sys_role_constraint` (
  `id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '约束ID',
  `constraint_type` VARCHAR(30) NOT NULL COMMENT '约束类型:MUTUAL_EXCLUSION-互斥, CARDINALITY-基数, PREREQUISITE-先决条件',
  `constraint_subtype` VARCHAR(30) DEFAULT NULL COMMENT '约束子类型:STATIC-静态, DYNAMIC-动态(互斥约束用)',
  `primary_role_id` BIGINT NOT NULL COMMENT '主角色ID(约束的主体角色)',
  `secondary_role_id` BIGINT DEFAULT NULL COMMENT '次角色ID(互斥约束的另一个角色,先决条件的前置角色)',
  `cardinality_limit` INT DEFAULT NULL COMMENT '基数限制值(基数约束用)',
  `constraint_scope` VARCHAR(20) NOT NULL COMMENT '约束范围:USER-用户级, ROLE-角色级',
  `description` VARCHAR(200) DEFAULT NULL COMMENT '约束描述',
  `status` TINYINT DEFAULT 1 COMMENT '状态:1-启用,0-禁用',
  `create_time` DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `update_time` DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
  PRIMARY KEY (`id`),
  KEY `idx_primary_role` (`primary_role_id`),
  KEY `idx_secondary_role` (`secondary_role_id`),
  KEY `idx_constraint_type` (`constraint_type`),
  -- 互斥约束需要两个角色ID
  CONSTRAINT `chk_mutual_exclusion_roles` CHECK (
    (`constraint_type` = 'MUTUAL_EXCLUSION' AND `secondary_role_id` IS NOT NULL) OR
    (`constraint_type` != 'MUTUAL_EXCLUSION')
  ),
  -- 基数约束需要限制值
  CONSTRAINT `chk_cardinality_limit` CHECK (
    (`constraint_type` = 'CARDINALITY' AND `cardinality_limit` IS NOT NULL) OR
    (`constraint_type` != 'CARDINALITY')
  )
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='角色约束表';
2.2.2 约束违规日志表 (sys_constraint_violation_log) - 新增
CREATE TABLE `sys_constraint_violation_log` (
  `id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '违规日志ID',
  `constraint_id` BIGINT NOT NULL COMMENT '违反的约束ID',
  `constraint_type` VARCHAR(30) NOT NULL COMMENT '约束类型',
  `user_id` BIGINT NOT NULL COMMENT '违规用户ID',
  `attempted_operation` VARCHAR(50) NOT NULL COMMENT '尝试的操作:ASSIGN_ROLE, ACTIVATE_ROLE, ACCESS_RESOURCE',
  `violation_details` JSON DEFAULT NULL COMMENT '违规详情(JSON格式)',
  `ip_address` VARCHAR(45) DEFAULT NULL COMMENT '操作IP地址',
  `create_time` DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '违规时间',
  PRIMARY KEY (`id`),
  KEY `idx_user_id` (`user_id`),
  KEY `idx_constraint_id` (`constraint_id`),
  KEY `idx_create_time` (`create_time`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='约束违规日志表';
2.2.3 角色表增强 (sys_role)
-- 在原有角色表基础上增加约束相关字段
ALTER TABLE `sys_role` 
ADD COLUMN `max_users` INT DEFAULT NULL COMMENT '最大用户数(基数约束)',
ADD COLUMN `min_prerequisite_roles` INT DEFAULT 0 COMMENT '最少前置角色数';

2.3 示例约束数据

2.3.1 互斥约束示例
-- 会计和出纳角色互斥(静态互斥)
INSERT INTO sys_role_constraint (
    constraint_type, 
    constraint_subtype, 
    primary_role_id, 
    secondary_role_id, 
    constraint_scope, 
    description
) VALUES 
('MUTUAL_EXCLUSION', 'STATIC', 4, 5, 'USER', '会计和出纳角色互斥'),
('MUTUAL_EXCLUSION', 'STATIC', 5, 4, 'USER', '出纳和会计角色互斥');

-- 系统管理员和普通用户角色互斥
INSERT INTO sys_role_constraint (
    constraint_type, 
    constraint_subtype, 
    primary_role_id, 
    secondary_role_id, 
    constraint_scope, 
    description
) VALUES 
('MUTUAL_EXCLUSION', 'STATIC', 6, 7, 'USER', '管理员和普通用户互斥');
2.3.2 基数约束示例
-- 系统管理员角色最多3个用户
INSERT INTO sys_role_constraint (
    constraint_type, 
    constraint_scope, 
    primary_role_id, 
    cardinality_limit, 
    description
) VALUES 
('CARDINALITY', 'ROLE', 6, 3, '系统管理员最多3人');

-- 每个用户最多拥有5个角色
INSERT INTO sys_role_constraint (
    constraint_type, 
    constraint_scope, 
    primary_role_id, 
    cardinality_limit, 
    description
) VALUES 
('CARDINALITY', 'USER', 0, 5, '用户角色数量限制'); -- primary_role_id=0表示全局约束
2.3.3 先决条件约束示例
-- 分配总监角色前必须先拥有经理角色
INSERT INTO sys_role_constraint (
    constraint_type, 
    constraint_scope, 
    primary_role_id, 
    secondary_role_id, 
    description
) VALUES 
('PREREQUISITE', 'USER', 3, 2, '总监角色需要先拥有经理角色');

-- 分配高级审计员前必须先拥有普通审计员
INSERT INTO sys_role_constraint (
    constraint_type, 
    constraint_scope, 
    primary_role_id, 
    secondary_role_id, 
    description
) VALUES 
('PREREQUISITE', 'USER', 8, 9, '高级审计员需要先拥有普通审计员角色');

三、RBAC2 工作流程详解

3.1 角色分配约束检查流程

管理员系统约束服务数据库分配角色给用户检查角色分配约束查询用户当前角色返回当前角色列表查询相关约束规则返回约束规则列表约束检查通过执行角色分配记录审计日志分配成功约束检查失败记录约束违规日志分配失败,返回具体约束信息alt[通过所有约束检查][违反约束规则]管理员系统约束服务数据库

3.2 权限验证约束检查流程

用户API接口权限服务约束服务请求敏感操作检查基础权限基础权限通过检查运行时约束验证动态互斥、时间、IP等约束约束检查通过执行操作成功约束检查失败返回约束违规错误alt[通过约束检查][违反约束]用户API接口权限服务约束服务

3.3 核心约束检查逻辑

3.3.1 互斥约束检查
  1. 获取用户当前所有角色
  2. 获取待分配角色的所有互斥角色
  3. 检查当前角色中是否包含互斥角色
  4. 如果包含,拒绝分配
3.3.2 基数约束检查
  • 用户基数:检查用户当前角色数量 + 新角色 ≤ 限制值
  • 角色基数:检查角色当前用户数量 + 新用户 ≤ 限制值
3.3.3 先决条件约束检查
  1. 获取待分配角色的所有先决条件角色
  2. 检查用户当前角色是否包含所有先决条件角色
  3. 如果不包含,拒绝分配

四、Java 实现示例(含详细中文注释)

4.1 约束相关实体类

角色约束实体
/**
 * 角色约束实体类
 * 对应数据库表:sys_role_constraint
 * 定义各种RBAC2约束规则
 */
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class RoleConstraint {
    /**
     * 约束ID,主键
     */
    private Long id;
    
    /**
     * 约束类型:
     * MUTUAL_EXCLUSION - 互斥约束
     * CARDINALITY - 基数约束  
     * PREREQUISITE - 先决条件约束
     */
    private String constraintType;
    
    /**
     * 约束子类型(主要用于互斥约束):
     * STATIC - 静态互斥(用户不能同时拥有)
     * DYNAMIC - 动态互斥(会话中不能同时激活)
     */
    private String constraintSubtype;
    
    /**
     * 主角色ID(约束的主体角色)
     * 例如:互斥约束中的会计角色ID
     *      基数约束中的被限制角色ID
     *      先决条件约束中的目标角色ID
     */
    private Long primaryRoleId;
    
    /**
     * 次角色ID(约束的关联角色):
     * 互斥约束:另一个互斥角色ID
     * 先决条件约束:前置角色ID
     * 基数约束:此字段为null
     */
    private Long secondaryRoleId;
    
    /**
     * 基数限制值(仅基数约束使用)
     * 例如:角色最多分配给3个用户
     *      用户最多拥有5个角色
     */
    private Integer cardinalityLimit;
    
    /**
     * 约束范围:
     * USER - 用户级约束(限制用户的行为)
     * ROLE - 角色级约束(限制角色的分配)
     */
    private String constraintScope;
    
    /**
     * 约束描述,用于显示和日志记录
     */
    private String description;
    
    /**
     * 约束状态:1-启用,0-禁用
     */
    private Integer status;
    
    /**
     * 创建时间
     */
    private LocalDateTime createTime;
    
    /**
     * 更新时间
     */
    private LocalDateTime updateTime;
}
约束违规日志实体
/**
 * 约束违规日志实体类
 * 对应数据库表:sys_constraint_violation_log
 * 记录所有违反约束规则的尝试
 */
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class ConstraintViolationLog {
    /**
     * 违规日志ID,主键
     */
    private Long id;
    
    /**
     * 违反的约束ID
     */
    private Long constraintId;
    
    /**
     * 约束类型(冗余存储,便于查询)
     */
    private String constraintType;
    
    /**
     * 违规用户ID
     */
    private Long userId;
    
    /**
     * 尝试的操作类型:
     * ASSIGN_ROLE - 分配角色
     * ACTIVATE_ROLE - 激活角色
     * ACCESS_RESOURCE - 访问资源
     */
    private String attemptedOperation;
    
    /**
     * 违规详情(JSON格式)
     * 包含违规的具体原因、相关角色、当前状态等
     */
    private String violationDetails;
    
    /**
     * 操作IP地址
     */
    private String ipAddress;
    
    /**
     * 违规时间
     */
    private LocalDateTime createTime;
}

4.2 约束服务核心实现

/**
 * RBAC2约束服务实现类
 * 负责各种约束规则的检查和验证
 */
@Service
@Slf4j
public class Rbac2ConstraintService {
    
    @Autowired
    private RoleConstraintMapper roleConstraintMapper;
    
    @Autowired
    private UserMapper userMapper;
    
    @Autowired
    private ConstraintViolationLogService violationLogService;
    
    /**
     * 检查分配角色给用户是否违反约束规则
     * 这是RBAC2的核心约束检查方法
     * 
     * 检查顺序:
     * 1. 互斥约束检查
     * 2. 基数约束检查  
     * 3. 先决条件约束检查
     * 
     * @param userId 用户ID
     * @param roleId 待分配的角色ID
     * @param operatorId 操作人ID(用于审计)
     * @param request 审计信息
     * @throws ConstraintViolationException 违反约束时抛出异常
     */
    public void checkRoleAssignmentConstraints(Long userId, Long roleId, 
                                             Long operatorId, AuditRequest request) 
            throws ConstraintViolationException {
        log.info("开始检查角色分配约束: userId={}, roleId={}", userId, roleId);
        
        try {
            // 1. 获取用户当前所有角色
            List<Long> currentUserRoleIds = userMapper.selectRoleIdsByUserId(userId);
            log.debug("用户当前角色ID列表: {}", currentUserRoleIds);
            
            // 2. 检查互斥约束
            checkMutualExclusionConstraints(userId, roleId, currentUserRoleIds, request);
            
            // 3. 检查基数约束
            checkCardinalityConstraints(userId, roleId, currentUserRoleIds, request);
            
            // 4. 检查先决条件约束
            checkPrerequisiteConstraints(userId, roleId, currentUserRoleIds, request);
            
            log.info("角色分配约束检查通过: userId={}, roleId={}", userId, roleId);
            
        } catch (ConstraintViolationException e) {
            // 记录约束违规日志
            violationLogService.logViolation(
                e.getConstraintId(),
                e.getConstraintType(),
                userId,
                "ASSIGN_ROLE",
                e.getViolationDetails(),
                request.getIpAddress()
            );
            throw e;
        } catch (Exception e) {
            log.error("约束检查发生异常", e);
            throw new ConstraintViolationException("约束检查失败", e);
        }
    }
    
    /**
     * 检查互斥约束
     * 确保用户不会同时拥有互斥角色
     * 
     * @param userId 用户ID
     * @param newRoleId 待分配的新角色ID
     * @param currentUserRoleIds 用户当前角色ID列表
     * @param request 审计请求
     * @throws ConstraintViolationException 违反互斥约束时抛出
     */
    private void checkMutualExclusionConstraints(Long userId, Long newRoleId,
                                               List<Long> currentUserRoleIds,
                                               AuditRequest request) 
            throws ConstraintViolationException {
        log.debug("开始检查互斥约束: newRoleId={}", newRoleId);
        
        // 查询新角色的所有互斥角色
        List<RoleConstraint> mutualExclusionConstraints = 
            roleConstraintMapper.selectMutualExclusionConstraints(newRoleId);
        
        if (CollectionUtils.isEmpty(mutualExclusionConstraints)) {
            log.debug("角色 {} 没有互斥约束", newRoleId);
            return;
        }
        
        // 检查用户当前角色是否包含任何互斥角色
        Set<Long> mutualExclusionRoleIds = mutualExclusionConstraints.stream()
                .map(RoleConstraint::getSecondaryRoleId)
                .collect(Collectors.toSet());
        
        for (Long currentRoleId : currentUserRoleIds) {
            if (mutualExclusionRoleIds.contains(currentRoleId)) {
                // 找到互斥角色,构建违规详情
                RoleConstraint violatedConstraint = mutualExclusionConstraints.stream()
                        .filter(c -> c.getSecondaryRoleId().equals(currentRoleId))
                        .findFirst()
                        .orElse(null);
                
                String violationDetails = buildViolationDetails(
                    "用户不能同时拥有互斥角色",
                    Map.of(
                        "userId", userId,
                        "newRoleId", newRoleId,
                        "existingRoleId", currentRoleId,
                        "constraintId", violatedConstraint != null ? violatedConstraint.getId() : null
                    )
                );
                
                log.warn("违反互斥约束: userId={}, newRoleId={}, existingRoleId={}", 
                        userId, newRoleId, currentRoleId);
                
                throw new ConstraintViolationException(
                    "角色分配违反互斥约束",
                    violatedConstraint != null ? violatedConstraint.getId() : null,
                    "MUTUAL_EXCLUSION",
                    violationDetails
                );
            }
        }
        
        log.debug("互斥约束检查通过");
    }
    
    /**
     * 检查基数约束
     * 包括用户角色数量限制和角色用户数量限制
     * 
     * @param userId 用户ID
     * @param newRoleId 待分配的新角色ID
     * @param currentUserRoleIds 用户当前角色ID列表
     * @param request 审计请求
     * @throws ConstraintViolationException 违反基数约束时抛出
     */
    private void checkCardinalityConstraints(Long userId, Long newRoleId,
                                           List<Long> currentUserRoleIds,
                                           AuditRequest request) 
            throws ConstraintViolationException {
        log.debug("开始检查基数约束");
        
        // 1. 检查用户角色数量限制(用户级基数约束)
        checkUserCardinalityConstraint(userId, newRoleId, currentUserRoleIds);
        
        // 2. 检查角色用户数量限制(角色级基数约束)
        checkRoleCardinalityConstraint(newRoleId);
        
        log.debug("基数约束检查通过");
    }
    
    /**
     * 检查用户角色数量限制
     */
    private void checkUserCardinalityConstraint(Long userId, Long newRoleId,
                                              List<Long> currentUserRoleIds) 
            throws ConstraintViolationException {
        // 查询用户级基数约束(primary_role_id = 0 表示全局用户约束)
        RoleConstraint userCardinalityConstraint = 
            roleConstraintMapper.selectUserCardinalityConstraint();
        
        if (userCardinalityConstraint == null) {
            return; // 没有用户基数约束
        }
        
        int newUserRoleCount = currentUserRoleIds.size() + 1;
        int limit = userCardinalityConstraint.getCardinalityLimit();
        
        if (newUserRoleCount > limit) {
            String violationDetails = buildViolationDetails(
                "用户角色数量超出限制",
                Map.of(
                    "userId", userId,
                    "currentRoleCount", currentUserRoleIds.size(),
                    "newRoleCount", newUserRoleCount,
                    "limit", limit,
                    "constraintId", userCardinalityConstraint.getId()
                )
            );
            
            log.warn("违反用户基数约束: userId={}, currentCount={}, newCount={}, limit={}", 
                    userId, currentUserRoleIds.size(), newUserRoleCount, limit);
            
            throw new ConstraintViolationException(
                String.format("用户最多只能拥有%d个角色", limit),
                userCardinalityConstraint.getId(),
                "CARDINALITY",
                violationDetails
            );
        }
    }
    
    /**
     * 检查角色用户数量限制
     */
    private void checkRoleCardinalityConstraint(Long roleId) 
            throws ConstraintViolationException {
        // 查询角色级基数约束
        RoleConstraint roleCardinalityConstraint = 
            roleConstraintMapper.selectRoleCardinalityConstraint(roleId);
        
        if (roleCardinalityConstraint == null) {
            return; // 没有角色基数约束
        }
        
        // 获取角色当前用户数量
        int currentUserCount = userMapper.selectUserCountByRoleId(roleId);
        int newTotalCount = currentUserCount + 1;
        int limit = roleCardinalityConstraint.getCardinalityLimit();
        
        if (newTotalCount > limit) {
            String violationDetails = buildViolationDetails(
                "角色用户数量超出限制",
                Map.of(
                    "roleId", roleId,
                    "currentUserCount", currentUserCount,
                    "newTotalCount", newTotalCount,
                    "limit", limit,
                    "constraintId", roleCardinalityConstraint.getId()
                )
            );
            
            log.warn("违反角色基数约束: roleId={}, currentCount={}, newCount={}, limit={}", 
                    roleId, currentUserCount, newTotalCount, limit);
            
            throw new ConstraintViolationException(
                String.format("角色最多只能分配给%d个用户", limit),
                roleCardinalityConstraint.getId(),
                "CARDINALITY",
                violationDetails
            );
        }
    }
    
    /**
     * 检查先决条件约束
     * 确保分配高级角色前用户已拥有必要的前置角色
     * 
     * @param userId 用户ID
     * @param newRoleId 待分配的新角色ID
     * @param currentUserRoleIds 用户当前角色ID列表
     * @param request 审计请求
     * @throws ConstraintViolationException 违反先决条件约束时抛出
     */
    private void checkPrerequisiteConstraints(Long userId, Long newRoleId,
                                            List<Long> currentUserRoleIds,
                                            AuditRequest request) 
            throws ConstraintViolationException {
        log.debug("开始检查先决条件约束: newRoleId={}", newRoleId);
        
        // 查询新角色的所有先决条件角色
        List<RoleConstraint> prerequisiteConstraints = 
            roleConstraintMapper.selectPrerequisiteConstraints(newRoleId);
        
        if (CollectionUtils.isEmpty(prerequisiteConstraints)) {
            log.debug("角色 {} 没有先决条件约束", newRoleId);
            return;
        }
        
        Set<Long> currentUserRoleSet = new HashSet<>(currentUserRoleIds);
        Set<Long> missingPrerequisites = new HashSet<>();
        
        // 检查是否缺少任何先决条件角色
        for (RoleConstraint constraint : prerequisiteConstraints) {
            Long prerequisiteRoleId = constraint.getSecondaryRoleId();
            if (!currentUserRoleSet.contains(prerequisiteRoleId)) {
                missingPrerequisites.add(prerequisiteRoleId);
            }
        }
        
        if (!missingPrerequisites.isEmpty()) {
            String violationDetails = buildViolationDetails(
                "缺少先决条件角色",
                Map.of(
                    "userId", userId,
                    "targetRoleId", newRoleId,
                    "missingPrerequisites", missingPrerequisites,
                    "currentRoles", currentUserRoleIds,
                    "constraintIds", prerequisiteConstraints.stream()
                            .map(RoleConstraint::getId)
                            .collect(Collectors.toList())
                )
            );
            
            log.warn("违反先决条件约束: userId={}, targetRoleId={}, missingPrerequisites={}", 
                    userId, newRoleId, missingPrerequisites);
            
            // 使用第一个约束的ID作为代表
            Long constraintId = prerequisiteConstraints.get(0).getId();
            
            throw new ConstraintViolationException(
                "分配角色前必须先拥有必要的前置角色",
                constraintId,
                "PREREQUISITE",
                violationDetails
            );
        }
        
        log.debug("先决条件约束检查通过");
    }
    
    /**
     * 构建违规详情JSON字符串
     */
    private String buildViolationDetails(String reason, Map<String, Object> details) {
        Map<String, Object> violationInfo = new HashMap<>();
        violationInfo.put("reason", reason);
        violationInfo.put("details", details);
        violationInfo.put("timestamp", LocalDateTime.now());
        
        try {
            return JsonUtils.toJson(violationInfo);
        } catch (Exception e) {
            log.error("构建违规详情JSON失败", e);
            return "{\"reason\":\"" + reason + "\",\"error\":\"JSON序列化失败\"}";
        }
    }
}

4.3 约束违规异常定义

/**
 * 约束违规异常
 * 用于表示违反RBAC2约束规则的情况
 */
@Data
@EqualsAndHashCode(callSuper = true)
public class ConstraintViolationException extends RuntimeException {
    
    /**
     * 违反的约束ID
     */
    private Long constraintId;
    
    /**
     * 约束类型
     */
    private String constraintType;
    
    /**
     * 违规详情
     */
    private String violationDetails;
    
    public ConstraintViolationException(String message, Long constraintId, 
                                      String constraintType, String violationDetails) {
        super(message);
        this.constraintId = constraintId;
        this.constraintType = constraintType;
        this.violationDetails = violationDetails;
    }
    
    public ConstraintViolationException(String message, Throwable cause) {
        super(message, cause);
    }
}

4.4 约束违规日志服务

/**
 * 约束违规日志服务
 * 负责记录所有违反约束规则的尝试
 */
@Service
@Slf4j
public class ConstraintViolationLogService {
    
    @Autowired
    private ConstraintViolationLogMapper violationLogMapper;
    
    /**
     * 记录约束违规日志
     * 
     * @param constraintId 约束ID
     * @param constraintType 约束类型
     * @param userId 违规用户ID
     * @param attemptedOperation 尝试的操作
     * @param violationDetails 违规详情
     * @param ipAddress 操作IP
     */
    public void logViolation(Long constraintId, String constraintType, 
                           Long userId, String attemptedOperation,
                           String violationDetails, String ipAddress) {
        try {
            ConstraintViolationLog log = ConstraintViolationLog.builder()
                    .constraintId(constraintId)
                    .constraintType(constraintType)
                    .userId(userId)
                    .attemptedOperation(attemptedOperation)
                    .violationDetails(violationDetails)
                    .ipAddress(ipAddress)
                    .build();
            
            violationLogMapper.insert(log);
            log.info("约束违规日志记录成功: userId={}, constraintType={}", userId, constraintType);
            
        } catch (Exception e) {
            log.error("记录约束违规日志失败", e);
            // 违规日志失败不应影响主业务流程
        }
    }
    
    /**
     * 查询用户的约束违规历史
     */
    public List<ConstraintViolationLog> getUserViolationHistory(Long userId) {
        return violationLogMapper.selectByUserId(userId);
    }
    
    /**
     * 查询特定约束的违规统计
     */
    public ViolationStatistics getConstraintViolationStats(Long constraintId) {
        return violationLogMapper.selectViolationStatsByConstraint(constraintId);
    }
}

五、API 使用示例

5.1 角色分配API(带约束检查)

/**
 * 角色管理Controller(支持RBAC2约束)
 */
@RestController
@RequestMapping("/api/role")
public class Rbac2RoleController {
    
    @Autowired
    private Rbac2ConstraintService constraintService;
    
    @Autowired
    private UserRoleService userRoleService;
    
    /**
     * 分配角色给用户(带RBAC2约束检查)
     * 
     * 请求示例:
     * POST /api/role/assign
     * {
     *   "userId": 1001,
     *   "roleId": 4  // 会计角色
     * }
     * 
     * 约束检查:
     * 1. 如果用户已拥有出纳角色(ID=5),则分配失败(互斥约束)
     * 2. 如果会计角色已分配给3个用户,则分配失败(基数约束)
     * 3. 如果分配总监角色但用户没有经理角色,则分配失败(先决条件)
     */
    @PostMapping("/assign")
    public Result<Void> assignRoleToUser(@RequestBody @Valid AssignRoleRequest request,
                                       HttpServletRequest httpRequest) {
        // 获取当前操作人
        Long currentUserId = getCurrentUserId(httpRequest);
        
        // 构建审计信息
        AuditRequest auditRequest = AuditRequest.builder()
                .ipAddress(getClientIpAddress(httpRequest))
                .userAgent(httpRequest.getHeader("User-Agent"))
                .build();
        
        try {
            // 1. 执行RBAC2约束检查
            constraintService.checkRoleAssignmentConstraints(
                request.getUserId(), 
                request.getRoleId(), 
                currentUserId, 
                auditRequest
            );
            
            // 2. 约束检查通过,执行角色分配
            userRoleService.assignRoleToUser(request.getUserId(), request.getRoleId());
            
            return Result.success();
            
        } catch (ConstraintViolationException e) {
            // 返回具体的约束违规信息
            log.warn("角色分配违反约束: {}", e.getMessage());
            return Result.error(ResultCode.CONSTRAINT_VIOLATION, e.getMessage())
                    .addData("constraintType", e.getConstraintType())
                    .addData("violationDetails", e.getViolationDetails());
        }
    }
    
    /**
     * 获取角色约束详情(用于前端展示)
     */
    @GetMapping("/{roleId}/constraints")
    public Result<RoleConstraintInfo> getRoleConstraints(@PathVariable Long roleId) {
        RoleConstraintInfo constraintInfo = buildRoleConstraintInfo(roleId);
        return Result.success(constraintInfo);
    }
}

5.2 约束管理API

/**
 * 约束管理Controller
 */
@RestController
@RequestMapping("/api/constraint")
public class ConstraintController {
    
    @Autowired
    private RoleConstraintService roleConstraintService;
    
    /**
     * 创建角色约束
     * 
     * 互斥约束示例:
     * POST /api/constraint
     * {
     *   "constraintType": "MUTUAL_EXCLUSION",
     *   "constraintSubtype": "STATIC",
     *   "primaryRoleId": 4,
     *   "secondaryRoleId": 5,
     *   "constraintScope": "USER",
     *   "description": "会计和出纳互斥"
     * }
     * 
     * 基数约束示例:
     * {
     *   "constraintType": "CARDINALITY",
     *   "constraintScope": "ROLE",
     *   "primaryRoleId": 6,
     *   "cardinalityLimit": 3,
     *   "description": "系统管理员最多3人"
     * }
     */
    @PostMapping
    public Result<Void> createConstraint(@RequestBody @Valid CreateConstraintRequest request) {
        roleConstraintService.createConstraint(request);
        return Result.success();
    }
    
    /**
     * 查询约束违规日志
     */
    @GetMapping("/violations")
    public Result<PageResult<ConstraintViolationLog>> getViolationLogs(
            ConstraintViolationQuery query) {
        PageResult<ConstraintViolationLog> result = 
            constraintViolationLogService.getViolationLogs(query);
        return Result.success(result);
    }
}

六、约束规则实现细节

6.1 互斥约束实现

静态互斥 vs 动态互斥
/**
 * 动态互斥约束检查(运行时检查)
 * 用于会话中角色激活的场景
 */
public boolean checkDynamicMutualExclusion(Long userId, Set<Long> activeRoleIds) {
    // 获取用户所有角色的动态互斥约束
    List<RoleConstraint> dynamicConstraints = 
        roleConstraintMapper.selectDynamicMutualExclusionConstraints(
            new ArrayList<>(activeRoleIds)
        );
    
    // 检查激活角色中是否包含互斥角色对
    Set<Long> activeRoleSet = new HashSet<>(activeRoleIds);
    for (RoleConstraint constraint : dynamicConstraints) {
        if (activeRoleSet.contains(constraint.getSecondaryRoleId())) {
            return false; // 发现互斥角色同时激活
        }
    }
    return true;
}

6.2 基数约束优化

缓存角色用户数量
/**
 * 使用Redis缓存角色用户数量,避免频繁数据库查询
 */
@Service
public class RoleCardinalityCacheService {
    
    @Autowired
    private RedisTemplate<String, Integer> redisTemplate;
    
    private static final String ROLE_USER_COUNT_KEY = "role:user:count:%d";
    
    /**
     * 获取角色用户数量(带缓存)
     */
    public int getRoleUserCount(Long roleId) {
        String key = String.format(ROLE_USER_COUNT_KEY, roleId);
        Integer count = redisTemplate.opsForValue().get(key);
        if (count == null) {
            // 缓存未命中,查询数据库
            count = userMapper.selectUserCountByRoleId(roleId);
            redisTemplate.opsForValue().set(key, count, 5, TimeUnit.MINUTES);
        }
        return count;
    }
    
    /**
     * 更新角色用户数量缓存
     */
    public void updateRoleUserCount(Long roleId) {
        String key = String.format(ROLE_USER_COUNT_KEY, roleId);
        int newCount = userMapper.selectUserCountByRoleId(roleId);
        redisTemplate.opsForValue().set(key, newCount, 5, TimeUnit.MINUTES);
    }
}

七、审计功能增强

7.1 约束相关审计日志

约束创建审计
{
  "operationType": "CREATE",
  "targetType": "CONSTRAINT",
  "targetId": 101,
  "operationDetails": {
    "constraintType": "MUTUAL_EXCLUSION",
    "primaryRoleId": 4,
    "secondaryRoleId": 5,
    "constraintScope": "USER",
    "description": "会计和出纳互斥"
  }
}
约束违规审计
{
  "operationType": "VIOLATION",
  "targetType": "CONSTRAINT",
  "targetId": 101,
  "operationDetails": {
    "userId": 1001,
    "attemptedOperation": "ASSIGN_ROLE",
    "violationReason": "用户不能同时拥有互斥角色",
    "currentRoles": [5],
    "attemptedRole": 4
  }
}

7.2 审计查询增强

/**
 * 审计日志查询支持约束相关操作
 */
public List<AuditLog> getConstraintRelatedAuditLogs(Long constraintId) {
    return auditLogMapper.selectByCondition(AuditLogQuery.builder()
            .targetType("CONSTRAINT")
            .targetId(constraintId)
            .build());
}

八、测试用例验证

8.1 互斥约束测试

@Test
void testMutualExclusionConstraint_ShouldPreventConflictingRoleAssignment() {
    // 准备数据:
    // - 用户ID=1001 已拥有出纳角色(ID=5)
    // - 会计角色(ID=4)和出纳角色(ID=5)互斥
    
    // 尝试分配会计角色给用户
    ConstraintViolationException exception = assertThrows(
        ConstraintViolationException.class,
        () -> constraintService.checkRoleAssignmentConstraints(1001L, 4L, 1L, createAuditRequest())
    );
    
    assertEquals("MUTUAL_EXCLUSION", exception.getConstraintType());
    assertTrue(exception.getMessage().contains("互斥约束"));
    
    // 验证违规日志已记录
    List<ConstraintViolationLog> logs = violationLogService.getUserViolationHistory(1001L);
    assertEquals(1, logs.size());
    assertEquals("ASSIGN_ROLE", logs.get(0).getAttemptedOperation());
}

8.2 基数约束测试

@Test
void testCardinalityConstraint_ShouldLimitRoleAssignments() {
    // 准备数据:
    // - 系统管理员角色(ID=6)基数限制为3
    // - 当前已有3个用户拥有该角色
    
    // 尝试分配第4个用户
    ConstraintViolationException exception = assertThrows(
        ConstraintViolationException.class,
        () -> constraintService.checkRoleAssignmentConstraints(1004L, 6L, 1L, createAuditRequest())
    );
    
    assertEquals("CARDINALITY", exception.getConstraintType());
    assertTrue(exception.getMessage().contains("最多只能分配给3个用户"));
}

8.3 先决条件约束测试

@Test
void testPrerequisiteConstraint_ShouldRequireBaseRoles() {
    // 准备数据:
    // - 用户ID=1001 没有任何角色
    // - 总监角色(ID=3)需要先拥有经理角色(ID=2)
    
    // 尝试直接分配总监角色
    ConstraintViolationException exception = assertThrows(
        ConstraintViolationException.class,
        () -> constraintService.checkRoleAssignmentConstraints(1001L, 3L, 1L, createAuditRequest())
    );
    
    assertEquals("PREREQUISITE", exception.getConstraintType());
    assertTrue(exception.getMessage().contains("前置角色"));
}

8.4 约束组合测试

@Test
void testMultipleConstraints_ShouldCheckAllConstraints() {
    // 准备复杂场景:
    // - 用户已拥有出纳角色(与会计互斥)
    // - 会计角色已达基数限制
    // - 用户缺少必要的前置角色
    
    // 应该优先报告互斥约束(第一个检查的约束)
    ConstraintViolationException exception = assertThrows(
        ConstraintViolationException.class,
        () -> constraintService.checkRoleAssignmentConstraints(1001L, 4L, 1L, createAuditRequest())
    );
    
    assertEquals("MUTUAL_EXCLUSION", exception.getConstraintType());
}

总结

RBAC2 模型通过引入约束机制,为权限系统提供了更强的业务规则控制能力,特别适用于对安全性要求较高的场景。

关键特性

  1. 互斥约束:确保职责分离,防止利益冲突
  2. 基数约束:控制权限分配的规模和范围
  3. 先决条件约束:保证权限分配的逻辑合理性
  4. 完整审计:记录所有约束相关操作和违规尝试

实施建议

  1. 约束粒度:根据业务需求合理设置约束粒度,避免过度约束
  2. 性能优化:对频繁检查的约束进行缓存
  3. 用户体验:提供清晰的约束违规提示信息
  4. 监控告警:对频繁的约束违规行为设置监控告警

RBAC2 是企业级权限系统的理想选择,能够有效支撑复杂的合规要求和安全策略。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

龙茶清欢

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值