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
🎯 总结
核心要点
- RBAC0:理解用户-角色-权限的基础关系
- RBAC1:掌握角色继承,减少重复配置
- RBAC2:应用约束条件,确保安全合规
- RBAC3:综合运用,构建完整的权限体系
实施步骤
1. 需求分析
├─ 识别用户类型
├─ 梳理业务功能
└─ 确定权限粒度
2. 角色设计
├─ 按职能划分角色
├─ 设计角色层级
└─ 定义角色约束
3. 权限分配
├─ 为角色分配权限
├─ 为用户分配角色
└─ 验证权限正确性
4. 系统实现
├─ 数据库设计
├─ 后端接口开发
└─ 前端权限控制
5. 测试优化
├─ 功能测试
├─ 性能优化
└─ 安全加固最佳实践清单
- ✅ 使用清晰的权限命名规范(资源:操作)
- ✅ 实现权限缓存,提升性能
- ✅ 记录审计日志,便于追溯
- ✅ 敏感操作二次验证
- ✅ 防止权限提升攻击
- ✅ 支持权限动态刷新
- ✅ 前后端权限一致性
- ✅ 定期权限审查
注意事项
⚠️ 避免过度设计
- 根据实际需求选择合适的模型
- 不是所有系统都需要 RBAC3
⚠️ 性能考虑
- 合理使用缓存
- 避免频繁的数据库查询
- 考虑使用异步处理
⚠️ 安全第一
- 最小权限原则
- 职责分离
- 定期审计
💡 下一步
学完 RBAC 后,你可以:
- 深入学习 ABAC(基于属性的访问控制)
- 研究 OAuth 2.0 和 OpenID Connect
- 探索 零信任架构
- 实践 微服务权限管理
- 了解 区块链权限管理
🤝 贡献
如果你发现文档中的问题或有改进建议,欢迎:
- 提交 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
