Skip to content

RBAC 从入门到实践的四级模型

RBAC(Role-Based Access Control,基于角色的访问控制)是最常用的权限管理模型。本文将系统介绍 RBAC 的四个级别模型,从基础到高级,帮助你构建完善的权限系统。

📋 目录


🎯 RBAC 基础概念

什么是 RBAC?

RBAC 是一种通过角色来管理用户权限的访问控制方法。它将权限与角色关联,再将角色分配给用户,从而简化权限管理。

核心要素

用户(User) → 角色(Role) → 权限(Permission) → 资源(Resource)
要素说明示例
用户(User)系统的使用者张三、李四
角色(Role)权限的集合管理员、编辑、访客
权限(Permission)对资源的操作查看、编辑、删除
资源(Resource)受保护的对象文章、订单、用户信息

传统 ACL vs RBAC

传统 ACL(Access Control List):
用户 → 权限
问题:用户多了,权限管理复杂

RBAC:
用户 → 角色 → 权限
优势:通过角色简化管理

🔰 RBAC0:核心模型

模型说明

RBAC0 是最基础的模型,定义了用户、角色、权限三者之间的关系。

核心关系

┌──────────┐       ┌──────────┐       ┌──────────┐
│   用户    │ ────> │   角色    │ ────> │   权限    │
│  (User)  │  分配  │  (Role)  │  拥有  │(Permission)│
└──────────┘       └──────────┘       └──────────┘
     1:N              1:N                1:N

数据库设计

sql
-- 用户表
CREATE TABLE users (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    username VARCHAR(50) NOT NULL UNIQUE,
    password VARCHAR(255) NOT NULL,
    email VARCHAR(100),
    status TINYINT DEFAULT 1 COMMENT '1:启用 0:禁用',
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

-- 角色表
CREATE TABLE roles (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    role_name VARCHAR(50) NOT NULL UNIQUE,
    role_code VARCHAR(50) NOT NULL UNIQUE,
    description VARCHAR(255),
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

-- 权限表
CREATE TABLE permissions (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    permission_name VARCHAR(50) NOT NULL,
    permission_code VARCHAR(50) NOT NULL UNIQUE,
    resource VARCHAR(50) COMMENT '资源标识',
    action VARCHAR(20) COMMENT '操作:view,create,update,delete',
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

-- 用户-角色关联表
CREATE TABLE user_roles (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    user_id BIGINT NOT NULL,
    role_id BIGINT NOT NULL,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE,
    FOREIGN KEY (role_id) REFERENCES roles(id) ON DELETE CASCADE,
    UNIQUE KEY uk_user_role (user_id, role_id)
);

-- 角色-权限关联表
CREATE TABLE role_permissions (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    role_id BIGINT NOT NULL,
    permission_id BIGINT NOT NULL,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    FOREIGN KEY (role_id) REFERENCES roles(id) ON DELETE CASCADE,
    FOREIGN KEY (permission_id) REFERENCES permissions(id) ON DELETE CASCADE,
    UNIQUE KEY uk_role_permission (role_id, permission_id)
);

Java 实现

java
/**
 * 用户实体
 */
@Entity
@Table(name = "users")
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    private String username;
    private String password;
    private String email;
    private Integer status;
    
    @ManyToMany(fetch = FetchType.LAZY)
    @JoinTable(
        name = "user_roles",
        joinColumns = @JoinColumn(name = "user_id"),
        inverseJoinColumns = @JoinColumn(name = "role_id")
    )
    private Set<Role> roles = new HashSet<>();
    
    // getters and setters
}

/**
 * 角色实体
 */
@Entity
@Table(name = "roles")
public class Role {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    private String roleName;
    private String roleCode;
    private String description;
    
    @ManyToMany(fetch = FetchType.LAZY)
    @JoinTable(
        name = "role_permissions",
        joinColumns = @JoinColumn(name = "role_id"),
        inverseJoinColumns = @JoinColumn(name = "permission_id")
    )
    private Set<Permission> permissions = new HashSet<>();
    
    // getters and setters
}

/**
 * 权限实体
 */
@Entity
@Table(name = "permissions")
public class Permission {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    private String permissionName;
    private String permissionCode;
    private String resource;
    private String action;
    
    // getters and setters
}

/**
 * RBAC0 权限检查服务
 */
@Service
public class RBAC0Service {
    
    @Autowired
    private UserRepository userRepository;
    
    /**
     * 检查用户是否有指定权限
     */
    public boolean hasPermission(Long userId, String permissionCode) {
        User user = userRepository.findById(userId)
            .orElseThrow(() -> new RuntimeException("用户不存在"));
        
        // 遍历用户的所有角色
        for (Role role : user.getRoles()) {
            // 检查角色是否包含该权限
            for (Permission permission : role.getPermissions()) {
                if (permission.getPermissionCode().equals(permissionCode)) {
                    return true;
                }
            }
        }

        
        return false;
    }
    
    /**
     * 获取用户的所有权限
     */
    public Set<String> getUserPermissions(Long userId) {
        User user = userRepository.findById(userId)
            .orElseThrow(() -> new RuntimeException("用户不存在"));
        
        Set<String> permissions = new HashSet<>();
        for (Role role : user.getRoles()) {
            for (Permission permission : role.getPermissions()) {
                permissions.add(permission.getPermissionCode());
            }
        }
        
        return permissions;
    }
}

Spring Security 集成

java
/**
 * 自定义权限注解
 */
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@PreAuthorize("@rbac0Service.hasPermission(principal.id, #permission)")
public @interface RequirePermission {
    String value();
}

/**
 * 控制器使用示例
 */
@RestController
@RequestMapping("/api/articles")
public class ArticleController {
    
    @GetMapping
    @RequirePermission("article:view")
    public List<Article> getArticles() {
        // 查看文章列表
        return articleService.findAll();
    }
    
    @PostMapping
    @RequirePermission("article:create")
    public Article createArticle(@RequestBody Article article) {
        // 创建文章
        return articleService.save(article);
    }
    
    @PutMapping("/{id}")
    @RequirePermission("article:update")
    public Article updateArticle(@PathVariable Long id, @RequestBody Article article) {
        // 更新文章
        return articleService.update(id, article);
    }
    
    @DeleteMapping("/{id}")
    @RequirePermission("article:delete")
    public void deleteArticle(@PathVariable Long id) {
        // 删除文章
        articleService.delete(id);
    }
}

初始化数据示例

java
@Component
public class RBAC0DataInitializer implements CommandLineRunner {
    
    @Autowired
    private RoleRepository roleRepository;
    
    @Autowired
    private PermissionRepository permissionRepository;
    
    @Override
    public void run(String... args) {
        // 创建权限
        Permission viewArticle = new Permission("查看文章", "article:view", "article", "view");
        Permission createArticle = new Permission("创建文章", "article:create", "article", "create");
        Permission updateArticle = new Permission("更新文章", "article:update", "article", "update");
        Permission deleteArticle = new Permission("删除文章", "article:delete", "article", "delete");
        
        permissionRepository.saveAll(Arrays.asList(
            viewArticle, createArticle, updateArticle, deleteArticle
        ));
        
        // 创建角色
        Role admin = new Role("管理员", "ADMIN", "系统管理员,拥有所有权限");
        admin.getPermissions().addAll(Arrays.asList(
            viewArticle, createArticle, updateArticle, deleteArticle
        ));
        
        Role editor = new Role("编辑", "EDITOR", "内容编辑,可以创建和编辑文章");
        editor.getPermissions().addAll(Arrays.asList(
            viewArticle, createArticle, updateArticle
        ));
        
        Role viewer = new Role("访客", "VIEWER", "普通用户,只能查看");
        viewer.getPermissions().add(viewArticle);
        
        roleRepository.saveAll(Arrays.asList(admin, editor, viewer));
    }
}

🔼 RBAC1:角色层级模型

模型说明

RBAC1 在 RBAC0 基础上增加了角色继承,形成角色层级结构。子角色可以继承父角色的所有权限。

角色层级示例

          ┌─────────────┐
          │  超级管理员  │ (所有权限)
          └──────┬──────┘

        ┌────────┴────────┐
        │                 │
   ┌────┴────┐      ┌────┴────┐
   │ 部门经理 │      │ 系统管理 │
   └────┬────┘      └────┬────┘
        │                │
   ┌────┴────┐      ┌────┴────┐
   │  组长   │      │  运维   │
   └────┬────┘      └─────────┘

   ┌────┴────┐
   │  组员   │
   └─────────┘

数据库设计扩展

sql
-- 角色表添加父角色字段
ALTER TABLE roles ADD COLUMN parent_id BIGINT;
ALTER TABLE roles ADD CONSTRAINT fk_role_parent 
    FOREIGN KEY (parent_id) REFERENCES roles(id);

-- 或者使用角色继承关系表(更灵活,支持多继承)
CREATE TABLE role_hierarchy (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    parent_role_id BIGINT NOT NULL,
    child_role_id BIGINT NOT NULL,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    FOREIGN KEY (parent_role_id) REFERENCES roles(id) ON DELETE CASCADE,
    FOREIGN KEY (child_role_id) REFERENCES roles(id) ON DELETE CASCADE,
    UNIQUE KEY uk_role_hierarchy (parent_role_id, child_role_id)
);

Java 实现

java
/**
 * 角色实体(支持继承)
 */
@Entity
@Table(name = "roles")
public class Role {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    private String roleName;
    private String roleCode;
    private String description;
    
    // 父角色(单继承)
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "parent_id")
    private Role parentRole;
    
    // 子角色列表
    @OneToMany(mappedBy = "parentRole", fetch = FetchType.LAZY)
    private Set<Role> childRoles = new HashSet<>();
    
    @ManyToMany(fetch = FetchType.LAZY)
    @JoinTable(
        name = "role_permissions",
        joinColumns = @JoinColumn(name = "role_id"),
        inverseJoinColumns = @JoinColumn(name = "permission_id")
    )
    private Set<Permission> permissions = new HashSet<>();
    
    // getters and setters
}

/**
 * RBAC1 权限检查服务
 */
@Service
public class RBAC1Service {
    
    @Autowired
    private UserRepository userRepository;
    
    /**
     * 检查用户是否有指定权限(支持角色继承)
     */
    public boolean hasPermission(Long userId, String permissionCode) {
        User user = userRepository.findById(userId)
            .orElseThrow(() -> new RuntimeException("用户不存在"));
        
        for (Role role : user.getRoles()) {
            if (hasPermissionInRoleHierarchy(role, permissionCode)) {
                return true;
            }
        }
        
        return false;
    }
    
    /**
     * 递归检查角色层级中是否包含权限
     */
    private boolean hasPermissionInRoleHierarchy(Role role, String permissionCode) {
        // 检查当前角色的权限
        for (Permission permission : role.getPermissions()) {
            if (permission.getPermissionCode().equals(permissionCode)) {
                return true;
            }
        }
        
        // 递归检查父角色的权限
        if (role.getParentRole() != null) {
            return hasPermissionInRoleHierarchy(role.getParentRole(), permissionCode);
        }
        
        return false;
    }
    
    /**
     * 获取角色的所有权限(包括继承的)
     */
    public Set<String> getRoleAllPermissions(Long roleId) {
        Role role = roleRepository.findById(roleId)
            .orElseThrow(() -> new RuntimeException("角色不存在"));
        
        Set<String> permissions = new HashSet<>();
        collectPermissionsFromHierarchy(role, permissions);
        return permissions;
    }
    
    private void collectPermissionsFromHierarchy(Role role, Set<String> permissions) {
        // 添加当前角色的权限
        for (Permission permission : role.getPermissions()) {
            permissions.add(permission.getPermissionCode());
        }
        
        // 递归添加父角色的权限
        if (role.getParentRole() != null) {
            collectPermissionsFromHierarchy(role.getParentRole(), permissions);
        }
    }
}

实际应用场景

java
/**
 * 企业组织架构示例
 */
public class OrganizationRoleExample {
    
    public void initializeOrganizationRoles() {
        // 1. 创建权限
        Permission viewReport = createPermission("查看报表", "report:view");
        Permission createReport = createPermission("创建报表", "report:create");
        Permission approveReport = createPermission("审批报表", "report:approve");
        Permission manageUser = createPermission("管理用户", "user:manage");
        Permission systemConfig = createPermission("系统配置", "system:config");
        
        // 2. 创建角色层级
        // 员工(基础角色)
        Role employee = createRole("员工", "EMPLOYEE");
        employee.getPermissions().add(viewReport);
        
        // 组长(继承员工)
        Role teamLeader = createRole("组长", "TEAM_LEADER");
        teamLeader.setParentRole(employee);  // 继承员工权限
        teamLeader.getPermissions().add(createReport);
        
        // 部门经理(继承组长)
        Role manager = createRole("部门经理", "MANAGER");
        manager.setParentRole(teamLeader);  // 继承组长和员工权限
        manager.getPermissions().add(approveReport);
        manager.getPermissions().add(manageUser);
        
        // 总经理(继承部门经理)
        Role ceo = createRole("总经理", "CEO");
        ceo.setParentRole(manager);  // 继承所有下级权限
        ceo.getPermissions().add(systemConfig);
        
        // 权限继承关系:
        // CEO -> 可以: systemConfig + approveReport + manageUser + createReport + viewReport
        // Manager -> 可以: approveReport + manageUser + createReport + viewReport
        // TeamLeader -> 可以: createReport + viewReport
        // Employee -> 可以: viewReport
    }
}

🔒 RBAC2:约束模型

模型说明

RBAC2 在 RBAC0 基础上增加了约束条件,用于限制角色分配和权限使用,确保职责分离。

约束类型

约束类型说明示例
互斥角色一个用户不能同时拥有两个互斥角色财务审批 vs 财务出纳
基数约束限制角色的用户数量系统管理员最多 3 人
先决条件分配角色前必须先拥有其他角色高级工程师需先是初级工程师
时间约束角色或权限的有效时间临时管理员权限 30 天
上下文约束基于环境条件的权限只能在办公网络使用

数据库设计扩展

sql
-- 角色约束表
CREATE TABLE role_constraints (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    constraint_type VARCHAR(50) NOT NULL COMMENT '约束类型:MUTEX,CARDINALITY,PREREQUISITE',
    constraint_name VARCHAR(100) NOT NULL,
    constraint_config JSON COMMENT '约束配置',
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

-- 互斥角色关系表
CREATE TABLE mutex_roles (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    role_id_1 BIGINT NOT NULL,
    role_id_2 BIGINT NOT NULL,
    description VARCHAR(255),
    FOREIGN KEY (role_id_1) REFERENCES roles(id),
    FOREIGN KEY (role_id_2) REFERENCES roles(id),
    UNIQUE KEY uk_mutex_roles (role_id_1, role_id_2)
);

-- 角色前置条件表
CREATE TABLE role_prerequisites (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    role_id BIGINT NOT NULL COMMENT '目标角色',
    prerequisite_role_id BIGINT NOT NULL COMMENT '前置角色',
    FOREIGN KEY (role_id) REFERENCES roles(id),
    FOREIGN KEY (prerequisite_role_id) REFERENCES roles(id)
);

-- 角色基数约束表
CREATE TABLE role_cardinality (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    role_id BIGINT NOT NULL UNIQUE,
    max_users INT NOT NULL COMMENT '最大用户数',
    current_users INT DEFAULT 0 COMMENT '当前用户数',
    FOREIGN KEY (role_id) REFERENCES roles(id)
);

-- 用户角色表添加时间约束
ALTER TABLE user_roles 
ADD COLUMN valid_from TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
ADD COLUMN valid_until TIMESTAMP NULL COMMENT '过期时间',
ADD COLUMN is_active TINYINT DEFAULT 1;

Java 实现

java
/**
 * 角色约束枚举
 */
public enum ConstraintType {
    MUTEX("互斥约束"),
    CARDINALITY("基数约束"),
    PREREQUISITE("前置条件约束"),
    TIME("时间约束"),
    CONTEXT("上下文约束");
    
    private String description;
    
    ConstraintType(String description) {
        this.description = description;
    }
}

/**
 * 约束验证服务
 */
@Service
public class RoleConstraintService {
    
    @Autowired
    private UserRoleRepository userRoleRepository;
    
    @Autowired
    private MutexRoleRepository mutexRoleRepository;
    
    @Autowired
    private RolePrerequisiteRepository prerequisiteRepository;
    
    @Autowired
    private RoleCardinalityRepository cardinalityRepository;
    
    /**
     * 验证是否可以为用户分配角色
     */
    public void validateRoleAssignment(Long userId, Long roleId) {
        // 1. 检查互斥约束
        validateMutexConstraint(userId, roleId);
        
        // 2. 检查基数约束
        validateCardinalityConstraint(roleId);
        
        // 3. 检查前置条件
        validatePrerequisiteConstraint(userId, roleId);
    }
    
    /**
     * 验证互斥约束
     */
    private void validateMutexConstraint(Long userId, Long roleId) {
        List<Long> userRoleIds = userRoleRepository.findRoleIdsByUserId(userId);
        List<MutexRole> mutexRoles = mutexRoleRepository.findByRoleId(roleId);
        
        for (MutexRole mutexRole : mutexRoles) {
            Long mutexRoleId = mutexRole.getRoleId1().equals(roleId) 
                ? mutexRole.getRoleId2() 
                : mutexRole.getRoleId1();
            
            if (userRoleIds.contains(mutexRoleId)) {
                throw new ConstraintViolationException(
                    String.format("角色互斥冲突:用户已拥有互斥角色 %d", mutexRoleId)
                );
            }
        }
    }
    
    /**
     * 验证基数约束
     */
    private void validateCardinalityConstraint(Long roleId) {
        RoleCardinality cardinality = cardinalityRepository.findByRoleId(roleId);
        if (cardinality != null) {
            int currentUsers = userRoleRepository.countByRoleId(roleId);
            if (currentUsers >= cardinality.getMaxUsers()) {
                throw new ConstraintViolationException(
                    String.format("角色用户数已达上限:%d/%d", 
                        currentUsers, cardinality.getMaxUsers())
                );
            }
        }
    }
    
    /**
     * 验证前置条件约束
     */
    private void validatePrerequisiteConstraint(Long userId, Long roleId) {
        List<RolePrerequisite> prerequisites = 
            prerequisiteRepository.findByRoleId(roleId);
        
        if (!prerequisites.isEmpty()) {
            List<Long> userRoleIds = userRoleRepository.findRoleIdsByUserId(userId);
            
            for (RolePrerequisite prerequisite : prerequisites) {
                if (!userRoleIds.contains(prerequisite.getPrerequisiteRoleId())) {
                    throw new ConstraintViolationException(
                        String.format("缺少前置角色:需要先拥有角色 %d", 
                            prerequisite.getPrerequisiteRoleId())
                    );
                }
            }
        }
    }
    
    /**
     * 验证时间约束
     */
    public boolean isRoleActive(UserRole userRole) {
        LocalDateTime now = LocalDateTime.now();
        
        if (userRole.getValidFrom() != null && now.isBefore(userRole.getValidFrom())) {
            return false;  // 还未生效
        }
        
        if (userRole.getValidUntil() != null && now.isAfter(userRole.getValidUntil())) {
            return false;  // 已过期
        }
        
        return userRole.getIsActive() == 1;
    }
}

/**
 * 用户角色分配服务(带约束验证)
 */
@Service
public class UserRoleService {
    
    @Autowired
    private UserRoleRepository userRoleRepository;
    
    @Autowired
    private RoleConstraintService constraintService;
    
    /**
     * 为用户分配角色
     */
    @Transactional
    public void assignRole(Long userId, Long roleId, LocalDateTime validUntil) {
        // 验证约束
        constraintService.validateRoleAssignment(userId, roleId);
        
        // 创建用户角色关联
        UserRole userRole = new UserRole();
        userRole.setUserId(userId);
        userRole.setRoleId(roleId);
        userRole.setValidFrom(LocalDateTime.now());
        userRole.setValidUntil(validUntil);
        userRole.setIsActive(1);
        
        userRoleRepository.save(userRole);
    }
}

实际应用场景

java
/**
 * 财务系统约束示例
 */
@Component
public class FinancialSystemConstraints {
    
    /**
     * 初始化财务系统的角色约束
     */
    public void initializeConstraints() {
        // 1. 互斥约束:财务审批人和出纳不能是同一人
        createMutexConstraint("财务审批", "财务出纳", "职责分离原则");
        
        // 2. 基数约束:系统管理员最多3人
        createCardinalityConstraint("系统管理员", 3);
        
        // 3. 前置条件:高级会计需要先是初级会计
        createPrerequisiteConstraint("高级会计", "初级会计");
        
        // 4. 时间约束:临时审计权限30天
        assignTemporaryRole(userId, "临时审计", 30);
    }
}

🎯 RBAC3:统一模型

模型说明

RBAC3 = RBAC1 + RBAC2,结合了角色层级约束条件,是最完整的 RBAC 模型。

模型架构

┌─────────────────────────────────────────────────┐
│                   RBAC3                         │
│                                                 │
│  ┌──────────────┐         ┌──────────────┐    │
│  │   RBAC1      │         │   RBAC2      │    │
│  │  角色层级     │    +    │  约束条件     │    │
│  └──────────────┘         └──────────────┘    │
│           │                      │             │
│           └──────────┬───────────┘             │
│                      │                         │
│              ┌───────┴────────┐               │
│              │     RBAC0      │               │
│              │   核心模型      │               │
│              └────────────────┘               │
└─────────────────────────────────────────────────┘

完整实现

java
/**
 * RBAC3 完整权限服务
 */
@Service
public class RBAC3Service {
    
    @Autowired
    private UserRepository userRepository;
    
    @Autowired
    private RoleConstraintService constraintService;
    
    /**
     * 检查用户权限(支持层级和约束)
     */
    public boolean hasPermission(Long userId, String permissionCode) {
        User user = userRepository.findById(userId)
            .orElseThrow(() -> new RuntimeException("用户不存在"));
        
        for (UserRole userRole : user.getUserRoles()) {
            // 1. 检查时间约束
            if (!constraintService.isRoleActive(userRole)) {
                continue;
            }
            
            // 2. 检查角色层级中的权限
            Role role = userRole.getRole();
            if (hasPermissionInHierarchy(role, permissionCode)) {
                return true;
            }
        }
        
        return false;
    }
    
    /**
     * 递归检查角色层级中的权限
     */
    private boolean hasPermissionInHierarchy(Role role, String permissionCode) {
        // 检查当前角色
        for (Permission permission : role.getPermissions()) {
            if (permission.getPermissionCode().equals(permissionCode)) {
                return true;
            }
        }
        
        // 递归检查父角色
        if (role.getParentRole() != null) {
            return hasPermissionInHierarchy(role.getParentRole(), permissionCode);
        }
        
        return false;
    }
    
    /**
     * 为用户分配角色(完整约束检查)
     */
    @Transactional
    public void assignRole(Long userId, Long roleId, LocalDateTime validUntil) {
        // 验证所有约束
        constraintService.validateRoleAssignment(userId, roleId);
        
        // 创建用户角色关联
        UserRole userRole = new UserRole();
        userRole.setUserId(userId);
        userRole.setRoleId(roleId);
        userRole.setValidFrom(LocalDateTime.now());
        userRole.setValidUntil(validUntil);
        
        userRoleRepository.save(userRole);
    }
    
    /**
     * 获取用户的有效权限列表
     */
    public Set<String> getUserEffectivePermissions(Long userId) {
        User user = userRepository.findById(userId)
            .orElseThrow(() -> new RuntimeException("用户不存在"));
        
        Set<String> permissions = new HashSet<>();
        
        for (UserRole userRole : user.getUserRoles()) {
            // 只统计有效的角色
            if (constraintService.isRoleActive(userRole)) {
                Role role = userRole.getRole();
                collectPermissionsFromHierarchy(role, permissions);
            }
        }
        
        return permissions;
    }
    
    private void collectPermissionsFromHierarchy(Role role, Set<String> permissions) {
        for (Permission permission : role.getPermissions()) {
            permissions.add(permission.getPermissionCode());
        }
        
        if (role.getParentRole() != null) {
            collectPermissionsFromHierarchy(role.getParentRole(), permissions);
        }
    }
}

💼 实战案例

案例1:企业 OA 系统

需求分析

一个典型的企业 OA 系统需要管理:

  • 员工、部门经理、总经理等不同层级
  • 请假审批、报销审批等业务流程
  • 财务、人事等敏感权限的职责分离

角色设计

java
/**
 * OA 系统角色初始化
 */
@Component
public class OASystemRoleInitializer {
    
    @Autowired
    private RoleService roleService;
    
    @Autowired
    private PermissionService permissionService;
    
    @Autowired
    private RoleConstraintService constraintService;
    
    @PostConstruct
    public void initialize() {
        // 1. 创建权限
        Map<String, Permission> permissions = createPermissions();
        
        // 2. 创建角色层级
        Map<String, Role> roles = createRoleHierarchy(permissions);
        
        // 3. 设置约束
        setupConstraints(roles);
    }
    
    private Map<String, Permission> createPermissions() {
        Map<String, Permission> permissions = new HashMap<>();
        
        // 基础权限
        permissions.put("view_notice", createPermission("查看公告", "notice:view"));
        permissions.put("create_notice", createPermission("发布公告", "notice:create"));
        
        // 请假权限
        permissions.put("apply_leave", createPermission("申请请假", "leave:apply"));
        permissions.put("approve_leave_team", createPermission("审批组内请假", "leave:approve:team"));
        permissions.put("approve_leave_dept", createPermission("审批部门请假", "leave:approve:dept"));
        permissions.put("approve_leave_all", createPermission("审批所有请假", "leave:approve:all"));
        
        // 报销权限
        permissions.put("apply_expense", createPermission("申请报销", "expense:apply"));
        permissions.put("approve_expense_team", createPermission("审批组内报销", "expense:approve:team"));
        permissions.put("approve_expense_dept", createPermission("审批部门报销", "expense:approve:dept"));
        permissions.put("finance_review", createPermission("财务复核", "expense:finance:review"));
        permissions.put("finance_payment", createPermission("财务付款", "expense:finance:payment"));
        
        // 人事权限
        permissions.put("view_salary", createPermission("查看薪资", "salary:view"));
        permissions.put("manage_salary", createPermission("管理薪资", "salary:manage"));
        
        // 系统权限
        permissions.put("system_config", createPermission("系统配置", "system:config"));
        permissions.put("user_manage", createPermission("用户管理", "user:manage"));
        
        return permissions;
    }
    
    private Map<String, Role> createRoleHierarchy(Map<String, Permission> perms) {
        Map<String, Role> roles = new HashMap<>();
        
        // 1. 员工(基础角色)
        Role employee = createRole("员工", "EMPLOYEE", "普通员工");
        employee.addPermissions(
            perms.get("view_notice"),
            perms.get("apply_leave"),
            perms.get("apply_expense")
        );
        roles.put("employee", employee);
        
        // 2. 组长(继承员工)
        Role teamLeader = createRole("组长", "TEAM_LEADER", "小组负责人");
        teamLeader.setParentRole(employee);
        teamLeader.addPermissions(
            perms.get("create_notice"),
            perms.get("approve_leave_team"),
            perms.get("approve_expense_team")
        );
        roles.put("team_leader", teamLeader);
        
        // 3. 部门经理(继承组长)
        Role manager = createRole("部门经理", "MANAGER", "部门负责人");
        manager.setParentRole(teamLeader);
        manager.addPermissions(
            perms.get("approve_leave_dept"),
            perms.get("approve_expense_dept"),
            perms.get("user_manage")
        );
        roles.put("manager", manager);
        
        // 4. 总经理(继承部门经理)
        Role ceo = createRole("总经理", "CEO", "公司最高管理者");
        ceo.setParentRole(manager);
        ceo.addPermissions(
            perms.get("approve_leave_all"),
            perms.get("view_salary"),
            perms.get("system_config")
        );
        roles.put("ceo", ceo);
        
        // 5. 财务专员(独立角色)
        Role finance = createRole("财务专员", "FINANCE", "财务部门");
        finance.setParentRole(employee);
        finance.addPermissions(
            perms.get("finance_review"),
            perms.get("view_salary")
        );
        roles.put("finance", finance);
        
        // 6. 出纳(独立角色)
        Role cashier = createRole("出纳", "CASHIER", "财务出纳");
        cashier.setParentRole(employee);
        cashier.addPermissions(
            perms.get("finance_payment")
        );
        roles.put("cashier", cashier);
        
        // 7. 人事专员(独立角色)
        Role hr = createRole("人事专员", "HR", "人力资源");
        hr.setParentRole(employee);
        hr.addPermissions(
            perms.get("manage_salary"),
            perms.get("user_manage")
        );
        roles.put("hr", hr);
        
        return roles;
    }
    
    private void setupConstraints(Map<String, Role> roles) {
        // 1. 互斥约束:财务专员和出纳不能是同一人
        createMutexConstraint(roles.get("finance"), roles.get("cashier"));
        
        // 2. 互斥约束:人事专员和部门经理不能是同一人(避免自己给自己调薪)
        createMutexConstraint(roles.get("hr"), roles.get("manager"));
        
        // 3. 基数约束:总经理只能有1人
        createCardinalityConstraint(roles.get("ceo"), 1);
        
        // 4. 基数约束:财务专员最多3人
        createCardinalityConstraint(roles.get("finance"), 3);
    }
}

业务流程示例

java
/**
 * 请假审批流程
 */
@Service
public class LeaveApprovalService {
    
    @Autowired
    private RBAC3Service rbacService;
    
    /**
     * 提交请假申请
     */
    public Leave applyLeave(Long userId, LeaveRequest request) {
        // 检查申请权限
        if (!rbacService.hasPermission(userId, "leave:apply")) {
            throw new PermissionDeniedException("没有请假申请权限");
        }
        
        Leave leave = new Leave();
        leave.setUserId(userId);
        leave.setStartDate(request.getStartDate());
        leave.setEndDate(request.getEndDate());
        leave.setReason(request.getReason());
        leave.setStatus("PENDING");
        
        // 根据请假天数确定审批流程
        int days = calculateDays(request.getStartDate(), request.getEndDate());
        if (days <= 3) {
            leave.setApprovalLevel("TEAM_LEADER");  // 组长审批
        } else if (days <= 7) {
            leave.setApprovalLevel("MANAGER");  // 部门经理审批
        } else {
            leave.setApprovalLevel("CEO");  // 总经理审批
        }
        
        return leaveRepository.save(leave);
    }
    
    /**
     * 审批请假
     */
    public void approveLeave(Long approverId, Long leaveId, boolean approved) {
        Leave leave = leaveRepository.findById(leaveId)
            .orElseThrow(() -> new RuntimeException("请假记录不存在"));
        
        // 检查审批权限
        String requiredPermission = getRequiredPermission(leave.getApprovalLevel());
        if (!rbacService.hasPermission(approverId, requiredPermission)) {
            throw new PermissionDeniedException("没有审批权限");
        }
        
        leave.setStatus(approved ? "APPROVED" : "REJECTED");
        leave.setApproverId(approverId);
        leave.setApprovalTime(LocalDateTime.now());
        
        leaveRepository.save(leave);
    }
    
    private String getRequiredPermission(String approvalLevel) {
        switch (approvalLevel) {
            case "TEAM_LEADER":
                return "leave:approve:team";
            case "MANAGER":
                return "leave:approve:dept";
            case "CEO":
                return "leave:approve:all";
            default:
                throw new IllegalArgumentException("无效的审批级别");
        }
    }
}

案例2:多租户 SaaS 平台

需求分析

  • 支持多个租户(企业)独立管理
  • 每个租户有自己的角色和权限体系
  • 平台管理员可以管理所有租户

数据库设计

sql
-- 租户表
CREATE TABLE tenants (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    tenant_name VARCHAR(100) NOT NULL,
    tenant_code VARCHAR(50) NOT NULL UNIQUE,
    status TINYINT DEFAULT 1,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

-- 用户表添加租户字段
ALTER TABLE users ADD COLUMN tenant_id BIGINT;
ALTER TABLE users ADD CONSTRAINT fk_user_tenant 
    FOREIGN KEY (tenant_id) REFERENCES tenants(id);

-- 角色表添加租户字段
ALTER TABLE roles ADD COLUMN tenant_id BIGINT;
ALTER TABLE roles ADD CONSTRAINT fk_role_tenant 
    FOREIGN KEY (tenant_id) REFERENCES tenants(id);

-- 添加租户隔离索引
CREATE INDEX idx_users_tenant ON users(tenant_id);
CREATE INDEX idx_roles_tenant ON roles(tenant_id);

Java 实现

java
/**
 * 多租户 RBAC 服务
 */
@Service
public class MultiTenantRBACService {
    
    @Autowired
    private RBAC3Service rbacService;
    
    /**
     * 检查权限(带租户隔离)
     */
    public boolean hasPermission(Long userId, Long tenantId, String permissionCode) {
        // 验证用户属于该租户
        User user = userRepository.findById(userId)
            .orElseThrow(() -> new RuntimeException("用户不存在"));
        
        if (!user.getTenantId().equals(tenantId)) {
            throw new TenantMismatchException("用户不属于该租户");
        }
        
        // 检查权限
        return rbacService.hasPermission(userId, permissionCode);
    }
    
    /**
     * 为租户创建角色
     */
    @Transactional
    public Role createTenantRole(Long tenantId, RoleRequest request) {
        Role role = new Role();
        role.setTenantId(tenantId);
        role.setRoleName(request.getRoleName());
        role.setRoleCode(request.getRoleCode());
        
        return roleRepository.save(role);
    }
}

/**
 * 租户上下文
 */
@Component
public class TenantContext {
    
    private static final ThreadLocal<Long> currentTenant = new ThreadLocal<>();
    
    public static void setTenantId(Long tenantId) {
        currentTenant.set(tenantId);
    }
    
    public static Long getTenantId() {
        return currentTenant.get();
    }
    
    public static void clear() {
        currentTenant.remove();
    }
}

/**
 * 租户拦截器
 */
@Component
public class TenantInterceptor implements HandlerInterceptor {
    
    @Override
    public boolean preHandle(HttpServletRequest request, 
                            HttpServletResponse response, 
                            Object handler) {
        // 从请求头获取租户ID
        String tenantId = request.getHeader("X-Tenant-Id");
        if (tenantId != null) {
            TenantContext.setTenantId(Long.parseLong(tenantId));
        }
        return true;
    }
    
    @Override
    public void afterCompletion(HttpServletRequest request, 
                               HttpServletResponse response, 
                               Object handler, 
                               Exception ex) {
        TenantContext.clear();
    }
}

🎓 最佳实践

1. 角色设计原则

按职能划分

✅ 推荐:
- 销售经理
- 技术主管
- 财务专员

❌ 避免:
- 张三的角色
- 临时角色123

最小权限原则

java
// 只授予必要的权限
Role viewer = new Role("访客", "VIEWER");
viewer.addPermission("article:view");  // ✅ 只能查看

// 避免过度授权
viewer.addPermission("article:delete");  // ❌ 不应该有删除权限

职责分离

java
// 财务系统的职责分离
createMutexConstraint("财务审批", "财务出纳");  // ✅
createMutexConstraint("采购申请", "采购审批");  // ✅

2. 权限命名规范

资源:操作 格式

java
// 推荐的命名方式
"article:view"      // 查看文章
"article:create"    // 创建文章
"article:update"    // 更新文章
"article:delete"    // 删除文章
"user:manage"       // 管理用户
"system:config"     // 系统配置

// 更细粒度的权限
"article:publish"   // 发布文章
"article:audit"     // 审核文章
"order:approve"     // 审批订单
"salary:view:self"  // 查看自己的薪资
"salary:view:all"   // 查看所有人薪资

3. 性能优化

权限缓存

java
/**
 * 权限缓存服务
 */
@Service
public class PermissionCacheService {
    
    @Autowired
    private RedisTemplate<String, Set<String>> redisTemplate;
    
    @Autowired
    private RBAC3Service rbacService;
    
    private static final String CACHE_PREFIX = "user:permissions:";
    private static final long CACHE_EXPIRE = 3600; // 1小时
    
    /**
     * 获取用户权限(带缓存)
     */
    public Set<String> getUserPermissions(Long userId) {
        String cacheKey = CACHE_PREFIX + userId;
        
        // 先从缓存获取
        Set<String> permissions = redisTemplate.opsForValue().get(cacheKey);
        if (permissions != null) {
            return permissions;
        }
        
        // 缓存未命中,从数据库查询
        permissions = rbacService.getUserEffectivePermissions(userId);
        
        // 写入缓存
        redisTemplate.opsForValue().set(cacheKey, permissions, 
            CACHE_EXPIRE, TimeUnit.SECONDS);
        
        return permissions;
    }
    
    /**
     * 清除用户权限缓存
     */
    public void clearUserPermissions(Long userId) {
        String cacheKey = CACHE_PREFIX + userId;
        redisTemplate.delete(cacheKey);
    }
    
    /**
     * 检查权限(带缓存)
     */
    public boolean hasPermission(Long userId, String permissionCode) {
        Set<String> permissions = getUserPermissions(userId);
        return permissions.contains(permissionCode);
    }
}

批量权限检查

java
/**
 * 批量权限检查
 */
@Service
public class BatchPermissionService {
    
    /**
     * 批量检查多个权限
     */
    public Map<String, Boolean> checkPermissions(Long userId, 
                                                 List<String> permissionCodes) {
        Set<String> userPermissions = permissionCacheService.getUserPermissions(userId);
        
        Map<String, Boolean> result = new HashMap<>();
        for (String code : permissionCodes) {
            result.put(code, userPermissions.contains(code));
        }
        
        return result;
    }
    
    /**
     * 检查是否拥有任一权限
     */
    public boolean hasAnyPermission(Long userId, String... permissionCodes) {
        Set<String> userPermissions = permissionCacheService.getUserPermissions(userId);
        
        for (String code : permissionCodes) {
            if (userPermissions.contains(code)) {
                return true;
            }
        }
        
        return false;
    }
    
    /**
     * 检查是否拥有所有权限
     */
    public boolean hasAllPermissions(Long userId, String... permissionCodes) {
        Set<String> userPermissions = permissionCacheService.getUserPermissions(userId);
        
        for (String code : permissionCodes) {
            if (!userPermissions.contains(code)) {
                return false;
            }
        }
        
        return true;
    }
}

4. 数据权限扩展

行级数据权限

java
/**
 * 数据权限实体
 */
@Entity
@Table(name = "data_permissions")
public class DataPermission {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    private Long roleId;
    private String resourceType;  // 资源类型:department, user, custom
    private String scopeType;     // 范围类型:ALL, DEPT, SELF, CUSTOM
    private String scopeValue;    // 范围值:部门ID、用户ID等
    
    // getters and setters
}

/**
 * 数据权限服务
 */
@Service
public class DataPermissionService {
    
    /**
     * 获取用户可见的数据范围
     */
    public String buildDataScopeCondition(Long userId, String resourceType) {
        User user = userRepository.findById(userId)
            .orElseThrow(() -> new RuntimeException("用户不存在"));
        
        StringBuilder condition = new StringBuilder();
        boolean hasCondition = false;
        
        for (Role role : user.getRoles()) {
            List<DataPermission> dataPermissions = 
                dataPermissionRepository.findByRoleIdAndResourceType(
                    role.getId(), resourceType);
            
            for (DataPermission dp : dataPermissions) {
                if (hasCondition) {
                    condition.append(" OR ");
                }
                
                switch (dp.getScopeType()) {
                    case "ALL":
                        return "1=1";  // 查看所有数据
                    case "DEPT":
                        condition.append("department_id = ").append(dp.getScopeValue());
                        break;
                    case "SELF":
                
                        condition.append("user_id = ").append(userId);
                        break;
                    case "CUSTOM":
                        condition.append(dp.getScopeValue());
                        break;
                }
                
                hasCondition = true;
            }
        }
        
        return hasCondition ? condition.toString() : "1=0";  // 无权限则返回假条件
    }
}

/**
 * 使用示例
 */
@Service
public class ArticleService {
    
    @Autowired
    private DataPermissionService dataPermissionService;
    
    /**
     * 查询用户可见的文章
     */
    public List<Article> findArticles(Long userId) {
        String condition = dataPermissionService.buildDataScopeCondition(
            userId, "article");
        
        String sql = "SELECT * FROM articles WHERE " + condition;
        return jdbcTemplate.query(sql, new ArticleRowMapper());
    }
}

5. 审计日志

java
/**
 * 权限审计日志
 */
@Entity
@Table(name = "permission_audit_logs")
public class PermissionAuditLog {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    private Long userId;
    private String username;
    private String action;          // 操作:ASSIGN_ROLE, REVOKE_ROLE, CHECK_PERMISSION
    private String resource;        // 资源
    private String permission;      // 权限
    private Boolean result;         // 结果:成功/失败
    private String ipAddress;       // IP地址
    private LocalDateTime createdAt;
    
    // getters and setters
}

/**
 * 审计日志服务
 */
@Service
public class PermissionAuditService {
    
    @Autowired
    private PermissionAuditLogRepository auditLogRepository;
    
    @Async
    public void logPermissionCheck(Long userId, String permission, 
                                   boolean result, HttpServletRequest request) {
        PermissionAuditLog log = new PermissionAuditLog();
        log.setUserId(userId);
        log.setAction("CHECK_PERMISSION");
        log.setPermission(permission);
        log.setResult(result);
        log.setIpAddress(getClientIp(request));
        log.setCreatedAt(LocalDateTime.now());
        
        auditLogRepository.save(log);
    }
    
    @Async
    public void logRoleAssignment(Long userId, Long roleId, 
                                 String action, HttpServletRequest request) {
        PermissionAuditLog log = new PermissionAuditLog();
        log.setUserId(userId);
        log.setAction(action);  // ASSIGN_ROLE or REVOKE_ROLE
        log.setResource("role:" + roleId);
        log.setIpAddress(getClientIp(request));
        log.setCreatedAt(LocalDateTime.now());
        
        auditLogRepository.save(log);
    }
    
    private String getClientIp(HttpServletRequest request) {
        String ip = request.getHeader("X-Forwarded-For");
        if (ip == null || ip.isEmpty()) {
            ip = request.getRemoteAddr();
        }
        return ip;
    }
}

6. 前端集成

按钮级权限控制

vue
<!-- Vue 示例 -->
<template>
  <div>
    <el-button 
      v-if="hasPermission('article:create')" 
      @click="createArticle">
      创建文章
    </el-button>
    
    <el-button 
      v-if="hasPermission('article:update')" 
      @click="updateArticle">
      编辑文章
    </el-button>
    
    <el-button 
      v-if="hasPermission('article:delete')" 
      @click="deleteArticle" 
      type="danger">
      删除文章
    </el-button>
  </div>
</template>

<script>
export default {
  data() {
    return {
      userPermissions: []
    }
  },
  
  created() {
    this.loadUserPermissions()
  },
  
  methods: {
    async loadUserPermissions() {
      const response = await this.$http.get('/api/user/permissions')
      this.userPermissions = response.data
    },
    
    hasPermission(permission) {
      return this.userPermissions.includes(permission)
    },
    
    hasAnyPermission(...permissions) {
      return permissions.some(p => this.userPermissions.includes(p))
    },
    
    hasAllPermissions(...permissions) {
      return permissions.every(p => this.userPermissions.includes(p))
    }
  }
}
</script>

自定义指令

javascript
// Vue 权限指令
Vue.directive('permission', {
  inserted(el, binding, vnode) {
    const { value } = binding
    const permissions = vnode.context.$store.state.user.permissions
    
    if (value && value instanceof Array && value.length > 0) {
      const hasPermission = permissions.some(permission => {
        return value.includes(permission)
      })
      
      if (!hasPermission) {
        el.parentNode && el.parentNode.removeChild(el)
      }
    } else {
      throw new Error('需要指定权限,如 v-permission="[\'article:create\']"')
    }
  }
})

// 使用示例
<el-button v-permission="['article:create']">创建文章</el-button>
<el-button v-permission="['article:update', 'article:delete']">编辑或删除</el-button>

React Hooks

typescript
// React 权限 Hook
import { useEffect, useState } from 'react'
import { getUserPermissions } from '@/api/user'

export function usePermission() {
  const [permissions, setPermissions] = useState<string[]>([])
  const [loading, setLoading] = useState(true)
  
  useEffect(() => {
    loadPermissions()
  }, [])
  
  const loadPermissions = async () => {
    try {
      const data = await getUserPermissions()
      setPermissions(data)
    } finally {
      setLoading(false)
    }
  }
  
  const hasPermission = (permission: string): boolean => {
    return permissions.includes(permission)
  }
  
  const hasAnyPermission = (...perms: string[]): boolean => {
    return perms.some(p => permissions.includes(p))
  }
  
  const hasAllPermissions = (...perms: string[]): boolean => {
    return perms.every(p => permissions.includes(p))
  }
  
  return {
    permissions,
    loading,
    hasPermission,
    hasAnyPermission,
    hasAllPermissions
  }
}

// 使用示例
function ArticleActions() {
  const { hasPermission, loading } = usePermission()
  
  if (loading) return <div>Loading...</div>
  
  return (
    <div>
      {hasPermission('article:create') && (
        <button onClick={createArticle}>创建文章</button>
      )}
      {hasPermission('article:update') && (
        <button onClick={updateArticle}>编辑文章</button>
      )}
      {hasPermission('article:delete') && (
        <button onClick={deleteArticle}>删除文章</button>
      )}
    </div>
  )
}

7. 安全建议

防止权限提升

java
/**
 * 防止权限提升攻击
 */
@Service
public class SecurityService {
    
    /**
     * 验证角色分配的安全性
     */
    public void validateRoleAssignmentSecurity(Long operatorId, 
                                              Long targetUserId, 
                                              Long roleId) {
        // 1. 检查操作者是否有分配该角色的权限
        if (!rbacService.hasPermission(operatorId, "role:assign")) {
            throw new SecurityException("没有分配角色的权限");
        }
        
        // 2. 防止分配比自己更高权限的角色
        Set<String> operatorPermissions = 
            rbacService.getUserEffectivePermissions(operatorId);
        
        Role targetRole = roleRepository.findById(roleId)
            .orElseThrow(() -> new RuntimeException("角色不存在"));
        
        Set<String> rolePermissions = getRoleAllPermissions(targetRole);
        
        // 检查是否有超出操作者权限的权限
        for (String permission : rolePermissions) {
            if (!operatorPermissions.contains(permission)) {
                throw new SecurityException(
                    "不能分配包含超出自己权限的角色"
                );
            }
        }
        
        // 3. 防止给自己分配角色
        if (operatorId.equals(targetUserId)) {
            throw new SecurityException("不能给自己分配角色");
        }
    }
}

敏感操作二次验证

java
/**
 * 敏感操作需要二次验证
 */
@Service
public class SensitiveOperationService {
    
    @Autowired
    private RedisTemplate<String, String> redisTemplate;
    
    /**
     * 发送验证码
     */
    public void sendVerificationCode(Long userId, String operation) {
        String code = generateRandomCode(6);
        String key = "verify:" + operation + ":" + userId;
        
        // 存储验证码,5分钟有效
        redisTemplate.opsForValue().set(key, code, 5, TimeUnit.MINUTES);
        
        // 发送验证码到用户手机或邮箱
        sendCode(userId, code);
    }
    
    /**
     * 验证操作
     */
    public boolean verifyOperation(Long userId, String operation, String code) {
        String key = "verify:" + operation + ":" + userId;
        String storedCode = redisTemplate.opsForValue().get(key);
        
        if (storedCode != null && storedCode.equals(code)) {
            redisTemplate.delete(key);
            return true;
        }
        
        return false;
    }
    
    /**
     * 执行敏感操作(需要验证)
     */
    @Transactional
    public void executeSensitiveOperation(Long userId, String operation, 
                                         String verifyCode, Runnable action) {
        // 验证权限
        if (!rbacService.hasPermission(userId, operation)) {
            throw new PermissionDeniedException("没有操作权限");
        }
        
        // 验证码验证
        if (!verifyOperation(userId, operation, verifyCode)) {
            throw new SecurityException("验证码错误或已过期");
        }
        
        // 执行操作
        action.run();
        
        // 记录审计日志
        auditService.logSensitiveOperation(userId, operation);
    }
}

📊 四级模型对比总结

模型特性适用场景复杂度
RBAC0用户-角色-权限基础模型简单应用,权限需求不复杂
RBAC1增加角色层级继承组织层级明确的企业应用⭐⭐
RBAC2增加约束条件需要职责分离的金融、政务系统⭐⭐⭐
RBAC3层级+约束的完整模型大型企业级应用,复杂权限场景⭐⭐⭐⭐

选择建议

小型应用(<100用户)
└─> RBAC0 足够

中型应用(100-1000用户,有组织层级)
└─> RBAC1

金融、政务等需要严格权限控制
└─> RBAC2 或 RBAC3

大型企业应用(>1000用户,复杂组织架构)
└─> RBAC3

🔧 常见问题

Q1: RBAC 与 ABAC 的区别?

RBAC(基于角色)

  • 优点:简单易懂,易于管理
  • 缺点:灵活性相对较低

ABAC(基于属性)

  • 优点:更灵活,可以基于多种属性判断
  • 缺点:实现复杂,性能开销大

建议:大多数场景使用 RBAC 即可,特殊需求可以结合 ABAC。

Q2: 如何处理临时权限?

java
// 方案1:使用时间约束
UserRole tempRole = new UserRole();
tempRole.setValidFrom(LocalDateTime.now());
tempRole.setValidUntil(LocalDateTime.now().plusDays(30));  // 30天后过期

// 方案2:使用临时角色
Role tempAdmin = createRole("临时管理员", "TEMP_ADMIN");
assignRole(userId, tempAdmin.getId(), 30);  // 30天后自动回收

Q3: 如何实现动态权限?

java
/**
 * 动态权限评估
 */
@Service
public class DynamicPermissionService {
    
    /**
     * 基于上下文的权限检查
     */
    public boolean hasPermissionWithContext(Long userId, 
                                           String permission, 
                                           Map<String, Object> context) {
        // 1. 基础权限检查
        if (!rbacService.hasPermission(userId, permission)) {
            return false;
        }
        
        // 2. 上下文检查
        if (permission.equals("article:update")) {
            // 只能编辑自己的文章
            Long articleAuthorId = (Long) context.get("authorId");
            return userId.equals(articleAuthorId);
        }
        
        if (permission.equals("order:approve")) {
            // 只能审批金额小于自己权限的订单
            BigDecimal amount = (BigDecimal) context.get("amount");
            BigDecimal userLimit = getUserApprovalLimit(userId);
            return amount.compareTo(userLimit) <= 0;
        }
        
        return true;
    }
}

Q4: 如何优化大量用户的权限查询?

java
/**
 * 权限预加载和缓存
 */
@Service
public class PermissionOptimizationService {
    
    @Autowired
    private RedisTemplate<String, Object> redisTemplate;
    
    /**
     * 批量预加载用户权限
     */
    @Async
    public void preloadUserPermissions(List<Long> userIds) {
        for (Long userId : userIds) {
            Set<String> permissions = rbacService.getUserEffectivePermissions(userId);
            String cacheKey = "user:permissions:" + userId;
            redisTemplate.opsForValue().set(cacheKey, permissions, 1, TimeUnit.HOURS);
        }
    }
    
    /**
     * 使用布隆过滤器快速判断
     */
    public boolean quickCheck(Long userId, String permission) {
        // 先用布隆过滤器快速判断
        if (!bloomFilter.mightContain(userId + ":" + permission)) {
            return false;  // 一定没有
        }
        
        // 可能有,再精确查询
        return rbacService.hasPermission(userId, permission);
    }
}

Q5: 如何处理权限变更后的缓存更新?

java
/**
 * 权限变更监听器
 */
@Component
public class PermissionChangeListener {
    
    @Autowired
    private PermissionCacheService cacheService;
    
    @Autowired
    private WebSocketService webSocketService;
    
    /**
     * 角色权限变更
     */
    @EventListener
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void onRolePermissionChanged(RolePermissionChangedEvent event) {
        Long roleId = event.getRoleId();
        
        // 1. 查找所有拥有该角色的用户
        List<Long> userIds = userRoleRepository.findUserIdsByRoleId(roleId);
        
        // 2. 清除这些用户的权限缓存
        for (Long userId : userIds) {
            cacheService.clearUserPermissions(userId);
            
            // 3. 通知前端刷新权限
            webSocketService.sendToUser(userId, new PermissionRefreshMessage());
        }
    }
    
    /**
     * 用户角色变更
     */
    @EventListener
    public void onUserRoleChanged(UserRoleChangedEvent event) {
        Long userId = event.getUserId();
        
        // 清除用户权限缓存
        cacheService.clearUserPermissions(userId);
        
        // 通知前端
        webSocketService.sendToUser(userId, new PermissionRefreshMessage());
    }
}

📚 参考资料

标准文档

开源实现

推荐阅读

  • 《企业应用架构模式》- Martin Fowler
  • 《微服务设计》- Sam Newman
  • 《领域驱动设计》- Eric Evans

🎯 总结

核心要点

  1. RBAC0:理解用户-角色-权限的基础关系
  2. RBAC1:掌握角色继承,减少重复配置
  3. RBAC2:应用约束条件,确保安全合规
  4. RBAC3:综合运用,构建完整的权限体系

实施步骤

1. 需求分析
   ├─ 识别用户类型
   ├─ 梳理业务功能
   └─ 确定权限粒度

2. 角色设计
   ├─ 按职能划分角色
   ├─ 设计角色层级
   └─ 定义角色约束

3. 权限分配
   ├─ 为角色分配权限
   ├─ 为用户分配角色
   └─ 验证权限正确性

4. 系统实现
   ├─ 数据库设计
   ├─ 后端接口开发
   └─ 前端权限控制

5. 测试优化
   ├─ 功能测试
   ├─ 性能优化
   └─ 安全加固

最佳实践清单

  • ✅ 使用清晰的权限命名规范(资源:操作)
  • ✅ 实现权限缓存,提升性能
  • ✅ 记录审计日志,便于追溯
  • ✅ 敏感操作二次验证
  • ✅ 防止权限提升攻击
  • ✅ 支持权限动态刷新
  • ✅ 前后端权限一致性
  • ✅ 定期权限审查

注意事项

⚠️ 避免过度设计

  • 根据实际需求选择合适的模型
  • 不是所有系统都需要 RBAC3

⚠️ 性能考虑

  • 合理使用缓存
  • 避免频繁的数据库查询
  • 考虑使用异步处理

⚠️ 安全第一

  • 最小权限原则
  • 职责分离
  • 定期审计

💡 下一步

学完 RBAC 后,你可以:

  1. 深入学习 ABAC(基于属性的访问控制)
  2. 研究 OAuth 2.0 和 OpenID Connect
  3. 探索 零信任架构
  4. 实践 微服务权限管理
  5. 了解 区块链权限管理

🤝 贡献

如果你发现文档中的问题或有改进建议,欢迎:

  • 提交 Issue
  • 发起 Pull Request
  • 分享你的实践经验

最后更新时间:2025-11-13

作者:DevPedia Team

许可证:MIT License


附录:完整代码示例

完整的 RBAC3 实现(Spring Boot)

java
// 1. 实体类
@Entity
@Table(name = "users")
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String username;
    private String password;
    
    @ManyToMany(fetch = FetchType.LAZY)
    @JoinTable(
        name = "user_roles",
        joinColumns = @JoinColumn(name = "user_id"),
        inverseJoinColumns = @JoinColumn(name = "role_id")
    )
    private Set<Role> roles = new HashSet<>();
}

@Entity
@Table(name = "roles")
public class Role {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String roleName;
    private String roleCode;
    
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "parent_id")
    private Role parentRole;
    
    @ManyToMany(fetch = FetchType.LAZY)
    @JoinTable(
        name = "role_permissions",
        joinColumns = @JoinColumn(name = "role_id"),
        inverseJoinColumns = @JoinColumn(name = "permission_id")
    )
    private Set<Permission> permissions = new HashSet<>();
}

@Entity
@Table(name = "permissions")
public class Permission {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String permissionName;
    private String permissionCode;
    private String resource;
    private String action;
}

// 2. Repository
public interface UserRepository extends JpaRepository<User, Long> {
    Optional<User> findByUsername(String username);
}

public interface RoleRepository extends JpaRepository<Role, Long> {
    Optional<Role> findByRoleCode(String roleCode);
}

public interface PermissionRepository extends JpaRepository<Permission, Long> {
    Optional<Permission> findByPermissionCode(String permissionCode);
}

// 3. Service
@Service
public class RBAC3ServiceImpl implements RBAC3Service {
    
    @Autowired
    private UserRepository userRepository;
    
    @Override
    public boolean hasPermission(Long userId, String permissionCode) {
        User user = userRepository.findById(userId)
            .orElseThrow(() -> new RuntimeException("用户不存在"));
        
        for (Role role : user.getRoles()) {
            if (hasPermissionInHierarchy(role, permissionCode)) {
                return true;
            }
        }
        
        return false;
    }
    
    private boolean hasPermissionInHierarchy(Role role, String permissionCode) {
        for (Permission permission : role.getPermissions()) {
            if (permission.getPermissionCode().equals(permissionCode)) {
                return true;
            }
        }
        
        if (role.getParentRole() != null) {
            return hasPermissionInHierarchy(role.getParentRole(), permissionCode);
        }
        
        return false;
    }
    
    @Override
    public Set<String> getUserEffectivePermissions(Long userId) {
        User user = userRepository.findById(userId)
            .orElseThrow(() -> new RuntimeException("用户不存在"));
        
        Set<String> permissions = new HashSet<>();
        for (Role role : user.getRoles()) {
            collectPermissionsFromHierarchy(role, permissions);
        }
        
        return permissions;
    }
    
    private void collectPermissionsFromHierarchy(Role role, Set<String> permissions) {
        for (Permission permission : role.getPermissions()) {
            permissions.add(permission.getPermissionCode());
        }
        
        if (role.getParentRole() != null) {
            collectPermissionsFromHierarchy(role.getParentRole(), permissions);
        }
    }
}

// 4. Controller
@RestController
@RequestMapping("/api/rbac")
public class RBACController {
    
    @Autowired
    private RBAC3Service rbacService;
    
    @GetMapping("/permissions")
    public Result<Set<String>> getUserPermissions() {
        Long userId = SecurityContextHolder.getCurrentUserId();
        Set<String> permissions = rbacService.getUserEffectivePermissions(userId);
        return Result.success(permissions);
    }
    
    @GetMapping("/check")
    public Result<Boolean> checkPermission(@RequestParam String permission) {
        Long userId = SecurityContextHolder.getCurrentUserId();
        boolean hasPermission = rbacService.hasPermission(userId, permission);
        return Result.success(hasPermission);
    }
}

// 5. 配置类
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    
    @Autowired
    private RBAC3Service rbacService;
    
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            .authorizeRequests()
                .antMatchers("/api/public/**").permitAll()
                .anyRequest().authenticated()
            .and()
            .formLogin()
            .and()
            .csrf().disable();
    }
    
    @Bean
    public PermissionEvaluator permissionEvaluator() {
        return new CustomPermissionEvaluator(rbacService);
    }
}

// 6. 自定义权限评估器
public class CustomPermissionEvaluator implements PermissionEvaluator {
    
    private final RBAC3Service rbacService;
    
    public CustomPermissionEvaluator(RBAC3Service rbacService) {
        this.rbacService = rbacService;
    }
    
    @Override
    public boolean hasPermission(Authentication authentication, 
                                Object targetDomainObject, 
                                Object permission) {
        if (authentication == null || !(permission instanceof String)) {
            return false;
        }
        
        Long userId = ((UserPrincipal) authentication.getPrincipal()).getId();
        return rbacService.hasPermission(userId, (String) permission);
    }
    
    @Override
    public boolean hasPermission(Authentication authentication, 
                                Serializable targetId, 
                                String targetType, 
                                Object permission) {
        return hasPermission(authentication, null, permission);
    }
}

SQL 初始化脚本

sql
-- 创建数据库
CREATE DATABASE rbac_demo CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;

USE rbac_demo;

-- 用户表
CREATE TABLE users (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    username VARCHAR(50) NOT NULL UNIQUE,
    password VARCHAR(255) NOT NULL,
    email VARCHAR(100),
    status TINYINT DEFAULT 1 COMMENT '1:启用 0:禁用',
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

-- 角色表
CREATE TABLE roles (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    role_name VARCHAR(50) NOT NULL,
    role_code VARCHAR(50) NOT NULL UNIQUE,
    description VARCHAR(255),
    parent_id BIGINT,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    FOREIGN KEY (parent_id) REFERENCES roles(id) ON DELETE SET NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

-- 权限表
CREATE TABLE permissions (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    permission_name VARCHAR(50) NOT NULL,
    permission_code VARCHAR(50) NOT NULL UNIQUE,
    resource VARCHAR(50),
    action VARCHAR(20),
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

-- 用户角色关联表
CREATE TABLE user_roles (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    user_id BIGINT NOT NULL,
    role_id BIGINT NOT NULL,
    valid_from TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    valid_until TIMESTAMP NULL,
    is_active TINYINT DEFAULT 1,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE,
    FOREIGN KEY (role_id) REFERENCES roles(id) ON DELETE CASCADE,
    UNIQUE KEY uk_user_role (user_id, role_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

-- 角色权限关联表
CREATE TABLE role_permissions (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    role_id BIGINT NOT NULL,
    permission_id BIGINT NOT NULL,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    FOREIGN KEY (role_id) REFERENCES roles(id) ON DELETE CASCADE,
    FOREIGN KEY (permission_id) REFERENCES permissions(id) ON DELETE CASCADE,
    UNIQUE KEY uk_role_permission (role_id, permission_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

-- 互斥角色表
CREATE TABLE mutex_roles (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    role_id_1 BIGINT NOT NULL,
    role_id_2 BIGINT NOT NULL,
    description VARCHAR(255),
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    FOREIGN KEY (role_id_1) REFERENCES roles(id) ON DELETE CASCADE,
    FOREIGN KEY (role_id_2) REFERENCES roles(id) ON DELETE CASCADE,
    UNIQUE KEY uk_mutex_roles (role_id_1, role_id_2)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

-- 角色基数约束表
CREATE TABLE role_cardinality (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    role_id BIGINT NOT NULL UNIQUE,
    max_users INT NOT NULL,
    current_users INT DEFAULT 0,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    FOREIGN KEY (role_id) REFERENCES roles(id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

-- 审计日志表
CREATE TABLE permission_audit_logs (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    user_id BIGINT,
    username VARCHAR(50),
    action VARCHAR(50),
    resource VARCHAR(100),
    permission VARCHAR(100),
    result TINYINT,
    ip_address VARCHAR(50),
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    INDEX idx_user_id (user_id),
    INDEX idx_created_at (created_at)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

-- 插入示例数据
-- 权限
INSERT INTO permissions (permission_name, permission_code, resource, action) VALUES
('查看文章', 'article:view', 'article', 'view'),
('创建文章', 'article:create', 'article', 'create'),
('更新文章', 'article:update', 'article', 'update'),
('删除文章', 'article:delete', 'article', 'delete'),
('发布文章', 'article:publish', 'article', 'publish'),
('管理用户', 'user:manage', 'user', 'manage'),
('系统配置', 'system:config', 'system', 'config');

-- 角色
INSERT INTO roles (role_name, role_code, description, parent_id) VALUES
('员工', 'EMPLOYEE', '普通员工', NULL),
('编辑', 'EDITOR', '内容编辑', 1),
('主编', 'CHIEF_EDITOR', '主编', 2),
('管理员', 'ADMIN', '系统管理员', NULL);

-- 角色权限关联
-- 员工:只能查看
INSERT INTO role_permissions (role_id, permission_id) VALUES (1, 1);

-- 编辑:可以创建和更新
INSERT INTO role_permissions (role_id, permission_id) VALUES (2, 2), (2, 3);

-- 主编:可以发布
INSERT INTO role_permissions (role_id, permission_id) VALUES (3, 5);

-- 管理员:所有权限
INSERT INTO role_permissions (role_id, permission_id) 
SELECT 4, id FROM permissions;

-- 用户
INSERT INTO users (username, password, email) VALUES
('admin', '$2a$10$...', 'admin@example.com'),
('editor', '$2a$10$...', 'editor@example.com'),
('user', '$2a$10$...', 'user@example.com');

-- 用户角色关联
INSERT INTO user_roles (user_id, role_id) VALUES
(1, 4),  -- admin -> 管理员
(2, 3),  -- editor -> 主编
(3, 1);  -- user -> 员工

🎓 学习路径

初学者路径

第1周:理解基础概念
├─ RBAC 的由来和优势
├─ 用户、角色、权限的关系
└─ RBAC0 核心模型

第2周:实践 RBAC0
├─ 数据库设计
├─ 后端接口开发
└─ 前端权限控制

第3周:学习 RBAC1
├─ 角色继承原理
├─ 层级设计方法
└─ 实现角色层级

第4周:掌握 RBAC2
├─ 约束类型
├─ 约束实现
└─ 安全加固

第5-6周:综合实践 RBAC3
├─ 完整系统实现
├─ 性能优化
└─ 项目部署

进阶学习

  • 🔹 研究 ABAC(基于属性的访问控制)
  • 🔹 学习 OAuth 2.0 和 OpenID Connect
  • 🔹 了解零信任安全架构
  • 🔹 探索微服务权限管理
  • 🔹 研究分布式权限系统

📖 延伸阅读

相关技术

  • ACL(Access Control List):访问控制列表,更细粒度的权限控制
  • MAC(Mandatory Access Control):强制访问控制,用于高安全场景
  • DAC(Discretionary Access Control):自主访问控制,资源所有者控制权限
  • ABAC(Attribute-Based Access Control):基于属性的访问控制,更灵活

企业级方案

  • Keycloak:开源的身份和访问管理解决方案
  • Okta:企业级身份管理平台
  • Auth0:现代化的身份验证和授权平台
  • AWS IAM:亚马逊云服务的身份和访问管理

🏆 结语

RBAC 是现代应用权限管理的基石,掌握 RBAC 的四级模型可以帮助你:

构建安全可靠的权限系统

  • 保护敏感数据和关键操作
  • 满足合规性要求
  • 提升系统安全性

提升开发效率

  • 规范的权限管理模式
  • 易于维护和扩展
  • 减少重复开发

优化用户体验

  • 清晰的权限边界
  • 灵活的角色分配
  • 便捷的权限管理

记住:权限管理不仅仅是技术实现,更是业务逻辑和安全策略的体现。在实际应用中,要根据具体需求选择合适的模型,并持续优化和完善。


祝你在权限管理的道路上越走越远! 🚀

如有问题或建议,欢迎交流讨论!


📧 联系方式

如有问题或建议,欢迎通过以下方式联系:

  • 💬 提交 Issue
  • 📮 发送邮件
  • 🌟 Star 本项目

感谢阅读!

Made with ❤️ by DevPedia Team

Released under the MIT License.