RBAC 基于角色的访问控制模型详解与实践指南

RBAC 权限控制模型详解与实践指南

一、RBAC 模型核心概念

1.1 什么是 RBAC?

RBAC(Role-Based Access Control,基于角色的访问控制)是一种权限管理模型,它将用户与权限通过角色进行关联,实现了权限管理的解耦和简化。

1.2 RBAC 发展历程

  • RBAC0:基础模型,包含用户、角色、权限三要素
  • RBAC1:支持角色继承
  • RBAC2:增加了约束条件
  • RBAC3:RBAC1 + RBAC2 的完整模型

1.3 核心概念对比

概念 说明 示例
用户(User) 系统的使用者 张三、李四
角色(Role) 权限的集合 管理员、编辑、访客
权限(Permission) 对资源的操作许可 用户:新增、文章:删除
会话(Session) 用户激活角色的临时环境 登录会话
约束(Constraint) 额外的限制规则 互斥角色、基数约束

二、RBAC 核心模型详解

2.1 RBAC0 - 基础模型

权限 = 操作 + 资源
分配
关联
用户 Users
角色 Roles
权限 Permissions
操作 Operations
资源 Resources
增删改查
用户/文章/订单

2.2 RBAC1 - 角色继承模型

超级管理员
部门管理员
审计员
普通员工
实习生
访客

2.3 RBAC2 - 约束模型

主要约束类型:

  1. 静态职责分离:用户不能同时拥有互斥角色
  2. 动态职责分离:同一会话中不能激活互斥角色
  3. 基数约束:角色被分配的用户数量限制
  4. 先决条件约束:必须先拥有某些角色才能获得其他角色

三、完整 RBAC 系统设计与实现

3.1 数据库设计

sql 复制代码
-- ============================================
-- 1. 核心表结构
-- ============================================

-- 用户表
CREATE TABLE sys_user (
id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT '用户ID',
username VARCHAR(50) NOT NULL UNIQUE COMMENT '用户名',
password VARCHAR(100) NOT NULL COMMENT '密码',
email VARCHAR(100) COMMENT '邮箱',
phone VARCHAR(20) COMMENT '手机号',
status TINYINT DEFAULT 1 COMMENT '状态: 0-禁用, 1-启用',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
INDEX idx_username(username),
INDEX idx_status(status)
) COMMENT '用户表';

-- 角色表
CREATE TABLE sys_role (
id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT '角色ID',
role_code VARCHAR(50) NOT NULL UNIQUE COMMENT '角色编码',
role_name VARCHAR(100) NOT NULL COMMENT '角色名称',
description VARCHAR(500) COMMENT '角色描述',
status TINYINT DEFAULT 1 COMMENT '状态: 0-禁用, 1-启用',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
INDEX idx_role_code(role_code)
) COMMENT '角色表';

-- 权限表
CREATE TABLE sys_permission (
id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT '权限ID',
permission_code VARCHAR(100) NOT NULL UNIQUE COMMENT '权限编码',
permission_name VARCHAR(100) NOT NULL COMMENT '权限名称',
permission_type TINYINT NOT NULL COMMENT '权限类型: 1-菜单, 2-按钮, 3-接口, 4-数据',
parent_id BIGINT DEFAULT 0 COMMENT '父权限ID',
path VARCHAR(200) COMMENT '路由路径/接口路径',
component VARCHAR(200) COMMENT '前端组件',
icon VARCHAR(50) COMMENT '图标',
sort INT DEFAULT 0 COMMENT '排序',
status TINYINT DEFAULT 1 COMMENT '状态: 0-禁用, 1-启用',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
INDEX idx_parent_id(parent_id),
INDEX idx_permission_code(permission_code)
) COMMENT '权限表';

-- ============================================
-- 2. 关系映射表
-- ============================================

-- 用户角色关联表
CREATE TABLE sys_user_role (
id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT '主键ID',
user_id BIGINT NOT NULL COMMENT '用户ID',
role_id BIGINT NOT NULL COMMENT '角色ID',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
UNIQUE KEY uk_user_role(user_id, role_id),
FOREIGN KEY (user_id) REFERENCES sys_user(id) ON DELETE CASCADE,
FOREIGN KEY (role_id) REFERENCES sys_role(id) ON DELETE CASCADE
) COMMENT '用户角色关联表';

-- 角色权限关联表
CREATE TABLE sys_role_permission (
id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT '主键ID',
role_id BIGINT NOT NULL COMMENT '角色ID',
permission_id BIGINT NOT NULL COMMENT '权限ID',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
UNIQUE KEY uk_role_permission(role_id, permission_id),
FOREIGN KEY (role_id) REFERENCES sys_role(id) ON DELETE CASCADE,
FOREIGN KEY (permission_id) REFERENCES sys_permission(id) ON DELETE CASCADE
) COMMENT '角色权限关联表';

-- ============================================
-- 3. 约束表(RBAC2 扩展)
-- ============================================

-- 互斥角色表
CREATE TABLE sys_mutex_role (
id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT '主键ID',
role_id_1 BIGINT NOT NULL COMMENT '角色1ID',
role_id_2 BIGINT NOT NULL COMMENT '角色2ID',
mutex_type TINYINT NOT NULL COMMENT '互斥类型: 1-静态互斥, 2-动态互斥',
description VARCHAR(500) COMMENT '描述',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
UNIQUE KEY uk_mutex_role(role_id_1, role_id_2),
FOREIGN KEY (role_id_1) REFERENCES sys_role(id),
FOREIGN KEY (role_id_2) REFERENCES sys_role(id),
CHECK (role_id_1 < role_id_2) -- 防止重复记录
) COMMENT '互斥角色表';

-- 角色层级表
CREATE TABLE sys_role_hierarchy (
id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT '主键ID',
parent_role_id BIGINT NOT NULL COMMENT '父角色ID',
child_role_id BIGINT NOT NULL COMMENT '子角色ID',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
UNIQUE KEY uk_role_hierarchy(parent_role_id, child_role_id),
FOREIGN KEY (parent_role_id) REFERENCES sys_role(id),
FOREIGN KEY (child_role_id) REFERENCES sys_role(id),
CHECK (parent_role_id != child_role_id)
) COMMENT '角色继承关系表';

-- ============================================
-- 4. 初始化数据
-- ============================================

-- 插入系统内置角色
INSERT INTO sys_role (role_code, role_name, description) VALUES
('super_admin', '超级管理员', '拥有系统所有权限'),
('admin', '管理员', '拥有大部分管理权限'),
('editor', '编辑', '内容编辑权限'),
('viewer', '查看者', '只读权限'),
('auditor', '审计员', '审计相关权限');

-- 插入权限数据(示例)
INSERT INTO sys_permission (permission_code, permission_name, permission_type, parent_id, path, component) VALUES
-- 系统管理菜单
('system:manage', '系统管理', 1, 0, '/system', 'Layout'),
('user:manage', '用户管理', 1, 1, '/system/user', 'system/user/index'),
('user:add', '新增用户', 2, 2, NULL, NULL),
('user:edit', '编辑用户', 2, 2, NULL, NULL),
('user:delete', '删除用户', 2, 2, NULL, NULL),
('user:view', '查看用户', 2, 2, NULL, NULL),

-- 角色管理菜单
('role:manage', '角色管理', 1, 1, '/system/role', 'system/role/index'),
('role:add', '新增角色', 2, 7, NULL, NULL),
('role:edit', '编辑角色', 2, 7, NULL, NULL),
('role:delete', '删除角色', 2, 7, NULL, NULL),
('role:view', '查看角色', 2, 7, NULL, NULL),

-- 权限管理菜单
('permission:manage', '权限管理', 1, 1, '/system/permission', 'system/permission/index'),

-- 内容管理菜单
('content:manage', '内容管理', 1, 0, '/content', 'Layout'),
('article:manage', '文章管理', 1, 13, '/content/article', 'content/article/index'),
('article:add', '新增文章', 2, 14, NULL, NULL),
('article:edit', '编辑文章', 2, 14, NULL, NULL),
('article:delete', '删除文章', 2, 14, NULL, NULL),
('article:publish', '发布文章', 2, 14, NULL, NULL),

-- 审计日志菜单
('audit:manage', '审计管理', 1, 0, '/audit', 'Layout');

-- 配置角色权限(超级管理员拥有所有权限)
INSERT INTO sys_role_permission (role_id, permission_id)
SELECT r.id, p.id
FROM sys_role r, sys_permission p
WHERE r.role_code = 'super_admin';

-- 配置互斥角色(管理员和审计员互斥)
INSERT INTO sys_mutex_role (role_id_1, role_id_2, mutex_type, description) VALUES
(
(SELECT id FROM sys_role WHERE role_code = 'admin'),
(SELECT id FROM sys_role WHERE role_code = 'auditor'),
1, '管理员和审计员不能由同一用户担任'
);

-- 配置角色继承(管理员继承编辑的权限)
INSERT INTO sys_role_hierarchy (parent_role_id, child_role_id) VALUES
(
(SELECT id FROM sys_role WHERE role_code = 'admin'),
(SELECT id FROM sys_role WHERE role_code = 'editor')
);

3.2 实体类设计

java 复制代码
// 用户实体
@Entity
@Table(name = "sys_user")
@Data
@EqualsAndHashCode(callSuper = true)
public class User extends BaseEntity {

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

@Column(nullable = false, unique = true, length = 50)
private String username;

@Column(nullable = false, length = 100)
private String password;

@Column(length = 100)
private String email;

@Column(length = 20)
private String phone;

@Column
private Integer status = 1; // 0-禁用, 1-启用

@ManyToMany(fetch = FetchType.LAZY)
@JoinTable(name = "sys_user_role",
joinColumns = @JoinColumn(name = "user_id"),
inverseJoinColumns = @JoinColumn(name = "role_id"))
@BatchSize(size = 20)
private Set<Role> roles = new HashSet<>();

@Transient
private Set<String> permissions = new HashSet<>();
}

// 角色实体
@Entity
@Table(name = "sys_role")
@Data
@EqualsAndHashCode(callSuper = true)
public class Role extends BaseEntity {

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

@Column(nullable = false, unique = true, length = 50)
private String roleCode;

@Column(nullable = false, length = 100)
private String roleName;

@Column(length = 500)
private String description;

@Column
private Integer status = 1;

@ManyToMany(fetch = FetchType.LAZY)
@JoinTable(name = "sys_role_permission",
joinColumns = @JoinColumn(name = "role_id"),
inverseJoinColumns = @JoinColumn(name = "permission_id"))
@BatchSize(size = 50)
private Set<Permission> permissions = new HashSet<>();

// 父角色(用于角色继承)
@ManyToMany(fetch = FetchType.LAZY)
@JoinTable(name = "sys_role_hierarchy",
joinColumns = @JoinColumn(name = "parent_role_id"),
inverseJoinColumns = @JoinColumn(name = "child_role_id"))
private Set<Role> childRoles = new HashSet<>();

@ManyToMany(mappedBy = "childRoles", fetch = FetchType.LAZY)
private Set<Role> parentRoles = new HashSet<>();
}

// 权限实体
@Entity
@Table(name = "sys_permission")
@Data
@EqualsAndHashCode(callSuper = true)
public class Permission extends BaseEntity {

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

@Column(nullable = false, unique = true, length = 100)
private String permissionCode;

@Column(nullable = false, length = 100)
private String permissionName;

@Column(nullable = false)
private Integer permissionType; // 1-菜单, 2-按钮, 3-接口, 4-数据

@Column
private Long parentId = 0L;

@Column(length = 200)
private String path;

@Column(length = 200)
private String component;

@Column(length = 50)
private String icon;

@Column
private Integer sort = 0;

@Column
private Integer status = 1;

@Transient
private List<Permission> children = new ArrayList<>();
}

// 互斥角色实体
@Entity
@Table(name = "sys_mutex_role")
@Data
public class MutexRole {

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "role_id_1", nullable = false)
private Role role1;

@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "role_id_2", nullable = false)
private Role role2;

@Column(nullable = false)
private Integer mutexType; // 1-静态互斥, 2-动态互斥

@Column(length = 500)
private String description;
}

四、Spring Security RBAC 集成实现

4.1 安全配置类

java 复制代码
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {

@Autowired
private UserDetailsService userDetailsService;

@Autowired
private JwtAuthenticationFilter jwtAuthenticationFilter;

@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}

@Override
protected void configure(HttpSecurity http) throws Exception {
http
// 禁用CSRF(因使用JWT)
.csrf().disable()

// 会话管理
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()

// 授权配置
.authorizeRequests()
// 公开接口
.antMatchers("/api/auth/**").permitAll()
.antMatchers("/api/public/**").permitAll()

// 需要认证的接口
.antMatchers("/api/**").authenticated()

// 管理接口需要特定权限
.antMatchers("/api/admin/**").hasRole("ADMIN")
.antMatchers("/api/system/**").hasAuthority("system:manage")

// 其他请求
.anyRequest().authenticated()
.and()

// 添加JWT过滤器
.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class)

// 异常处理
.exceptionHandling()
.authenticationEntryPoint(new JwtAuthenticationEntryPoint())
.accessDeniedHandler(new JwtAccessDeniedHandler());
}

@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService)
.passwordEncoder(passwordEncoder());
}

@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
}

4.2 自定义 UserDetailsService

java 复制代码
@Service
@Slf4j
public class UserDetailsServiceImpl implements UserDetailsService {

@Autowired
private UserService userService;

@Autowired
private RoleService roleService;

@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
// 1. 查询用户信息
User user = userService.findByUsername(username);
if (user == null) {
throw new UsernameNotFoundException("用户不存在: " + username);
}

// 2. 检查用户状态
if (user.getStatus() == 0) {
throw new DisabledException("用户已被禁用");
}

// 3. 查询用户角色和权限
List<GrantedAuthority> authorities = new ArrayList<>();

// 获取用户所有角色(包括继承的角色)
Set<Role> roles = getInheritedRoles(user.getRoles());

// 将角色转换为 Spring Security 的权限表示
for (Role role : roles) {
authorities.add(new SimpleGrantedAuthority("ROLE_" + role.getRoleCode().toUpperCase()));

// 获取角色所有权限
for (Permission permission : role.getPermissions()) {
if (permission.getStatus() == 1) {
authorities.add(new SimpleGrantedAuthority(permission.getPermissionCode()));
}
}
}

// 4. 构建 UserDetails 对象
return org.springframework.security.core.userdetails.User
.withUsername(user.getUsername())
.password(user.getPassword())
.authorities(authorities)
.accountExpired(false)
.accountLocked(false)
.credentialsExpired(false)
.disabled(user.getStatus() == 0)
.build();
}

/**
* 获取用户所有角色(包括继承的角色)
*/
private Set<Role> getInheritedRoles(Set<Role> directRoles) {
Set<Role> allRoles = new HashSet<>(directRoles);
Queue<Role> queue = new LinkedList<>(directRoles);

while (!queue.isEmpty()) {
Role currentRole = queue.poll();

// 获取当前角色的所有子角色(继承的角色)
Set<Role> childRoles = roleService.findChildRoles(currentRole.getId());

for (Role childRole : childRoles) {
if (!allRoles.contains(childRole)) {
allRoles.add(childRole);
queue.offer(childRole);
}
}
}

return allRoles;
}
}

4.3 权限注解使用

java 复制代码
@RestController
@RequestMapping("/api/user")
public class UserController {

@Autowired
private UserService userService;

/**
* 查看用户列表 - 需要 user:view 权限
*/
@PreAuthorize("hasAuthority('user:view')")
@GetMapping("/list")
public Result<List<UserVO>> listUsers(@RequestParam(defaultValue = "1") Integer page,
@RequestParam(defaultValue = "10") Integer size) {
Page<User> users = userService.listUsers(page, size);
return Result.success(users.map(this::convertToVO));
}

/**
* 新增用户 - 需要 user:add 权限
*/
@PreAuthorize("hasAuthority('user:add')")
@PostMapping("/add")
public Result<Void> addUser(@Valid @RequestBody CreateUserRequest request) {
userService.createUser(request);
return Result.success();
}

/**
* 编辑用户 - 需要 user:edit 权限
*/
@PreAuthorize("hasAuthority('user:edit')")
@PutMapping("/{userId}")
public Result<Void> updateUser(@PathVariable Long userId,
@Valid @RequestBody UpdateUserRequest request) {
userService.updateUser(userId, request);
return Result.success();
}

/**
* 删除用户 - 需要 user:delete 权限
*/
@PreAuthorize("hasAuthority('user:delete')")
@DeleteMapping("/{userId}")
public Result<Void> deleteUser(@PathVariable Long userId) {
userService.deleteUser(userId);
return Result.success();
}

/**
* 分配角色 - 需要 user:edit 和 role:view 权限
*/
@PreAuthorize("hasAuthority('user:edit') and hasAuthority('role:view')")
@PostMapping("/{userId}/roles")
public Result<Void> assignRoles(@PathVariable Long userId,
@RequestBody AssignRolesRequest request) {
userService.assignRoles(userId, request.getRoleIds());
return Result.success();
}

/**
* 数据权限控制示例
*/
@DataPermission(deptAlias = "d", userAlias = "u")
@GetMapping("/dept/{deptId}")
public Result<List<UserVO>> getUsersByDept(@PathVariable Long deptId) {
// 实际SQL会通过拦截器自动添加数据权限条件
return Result.success(userService.getUsersByDept(deptId));
}
}

/**
* 自定义数据权限注解
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface DataPermission {

/**
* 部门表的别名
*/
String deptAlias() default "";

/**
* 用户表的别名
*/
String userAlias() default "";

/**
* 权限类型
*/
DataScopeType scopeType() default DataScopeType.DEPT;
}

/**
* 数据权限范围类型
*/
public enum DataScopeType {
ALL,// 全部数据权限
DEPT,// 本部门数据
DEPT_AND_CHILD, // 本部门及子部门数据
SELF// 仅本人数据
}

4.4 权限拦截器实现

java 复制代码
@Component
public class DataPermissionInterceptor {

@Autowired
private PermissionService permissionService;

/**
* 处理数据权限拦截
*/
public String intercept(JoinPoint joinPoint, DataPermission dataPermission) {
// 1. 获取当前用户
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
String username = authentication.getName();

// 2. 获取用户的数据权限范围
DataScopeType scopeType = getUserDataScope(username);

// 3. 构建SQL条件
StringBuilder sqlCondition = new StringBuilder();

switch (scopeType) {
case ALL:
// 无限制
break;
case DEPT:
sqlCondition.append(dataPermission.deptAlias())
.append(".dept_id = #{userDeptId}");
break;
case DEPT_AND_CHILD:
sqlCondition.append(dataPermission.deptAlias())
.append(".dept_id IN (")
.append("SELECT id FROM sys_dept WHERE FIND_IN_SET(id, getDeptChildList(#{userDeptId}))")
.append(")");
break;
case SELF:
sqlCondition.append(dataPermission.userAlias())
.append(".id = #{userId}");
break;
default:
throw new IllegalArgumentException("未知的数据权限类型");
}

return sqlCondition.toString();
}

/**
* 获取用户的数据权限范围
*/
private DataScopeType getUserDataScope(String username) {
// 查询用户的数据权限配置
// 这里可以缓存用户的数据权限配置
return permissionService.getUserDataScope(username);
}
}

/**
* 数据权限AOP拦截器
*/
@Aspect
@Component
@Slf4j
public class DataPermissionAspect {

@Autowired
private DataPermissionInterceptor interceptor;

@Pointcut("@annotation(com.example.rbac.annotation.DataPermission)")
public void dataPermissionPointcut() {}

@Around("dataPermissionPointcut()")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
// 1. 获取注解信息
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Method method = signature.getMethod();
DataPermission dataPermission = method.getAnnotation(DataPermission.class);

// 2. 获取SQL条件
String condition = interceptor.intercept(joinPoint, dataPermission);

// 3. 设置ThreadLocal中的SQL条件
DataPermissionContext.setCondition(condition);

try {
// 4. 执行原方法
return joinPoint.proceed();
} finally {
// 5. 清理ThreadLocal
DataPermissionContext.clear();
}
}
}

五、前后端分离的RBAC实现

5.1 前端路由权限控制

typescript 复制代码
// permission.ts - 权限验证工具
import { RouteRecordRaw } from 'vue-router'

// 权限编码映射表
const permissionMap = new Map<string, string>([
['dashboard', 'dashboard:view'],
['user', 'user:manage'],
['user.add', 'user:add'],
['user.edit', 'user:edit'],
['user.delete', 'user:delete'],
['role', 'role:manage'],
['role.add', 'role:add'],
['role.edit', 'role:edit'],
['role.delete', 'role:delete'],
['permission', 'permission:manage'],
['article', 'article:manage'],
['article.add', 'article:add'],
['article.edit', 'article:edit'],
['article.delete', 'article:delete'],
['audit', 'audit:manage']
])

// 用户权限存储
class PermissionStore {
private permissions: Set<string> = new Set()
private roles: Set<string> = new Set()

// 设置权限
setPermissions(perms: string[]) {
this.permissions.clear()
perms.forEach(perm => this.permissions.add(perm))
}

// 设置角色
setRoles(roles: string[]) {
this.roles.clear()
roles.forEach(role => this.roles.add(role))
}

// 检查权限
hasPermission(perm: string): boolean {
return this.permissions.has(perm)
}

// 检查角色
hasRole(role: string): boolean {
return this.roles.has(role)
}

// 检查多个权限(全部满足)
hasAllPermissions(...perms: string[]): boolean {
return perms.every(perm => this.hasPermission(perm))
}

// 检查多个权限(满足任意一个)
hasAnyPermission(...perms: string[]): boolean {
return perms.some(perm => this.hasPermission(perm))
}

// 过滤有权限的路由
filterRoutes(routes: RouteRecordRaw[]): RouteRecordRaw[] {
return routes.filter(route => {
if (route.meta?.requireAuth === false) {
return true
}

const permission = permissionMap.get(route.name as string)
if (!permission) {
return true // 没有配置权限的路由默认可见
}

return this.hasPermission(permission)
}).map(route => {
if (route.children) {
return {
...route,
children: this.filterRoutes(route.children)
}
}
return route
})
}
}

export const permissionStore = new PermissionStore()

// 路由守卫
export function createPermissionGuard(router: Router) {
router.beforeEach(async (to, from, next) => {
// 1. 不需要权限的路由直接通过
if (to.meta?.requireAuth === false) {
return next()
}

// 2. 检查是否登录
const token = localStorage.getItem('token')
if (!token) {
return next('/login')
}

// 3. 检查是否有用户信息
const userInfo = userStore.userInfo
if (!userInfo) {
try {
// 获取用户信息
await userStore.getUserInfo()

// 获取权限和角色
await permissionStore.setPermissions(userStore.permissions)
await permissionStore.setRoles(userStore.roles)

// 动态添加路由
const filteredRoutes = permissionStore.filterRoutes(asyncRoutes)
filteredRoutes.forEach(route => {
router.addRoute(route)
})

// 重新跳转到目标路由
return next({ ...to, replace: true })
} catch (error) {
// 获取用户信息失败,清除token重新登录
localStorage.removeItem('token')
return next('/login')
}
}

// 4. 检查是否有权限访问该路由
const permission = permissionMap.get(to.name as string)
if (permission && !permissionStore.hasPermission(permission)) {
return next('/403') // 无权限页面
}

next()
})
}

// Vue组件中使用权限控制
<template>
<div>
<!-- 使用v-permission指令控制按钮显示 -->
<button v-permission="'user:add'" @click="handleAdd">新增用户</button>
<button v-permission="'user:edit'" @click="handleEdit">编辑用户</button>
<button v-permission="'user:delete'" @click="handleDelete">删除用户</button>

<!-- 使用v-role指令 -->
<div v-role="'admin'">
只有管理员可见的内容
</div>

<!-- 使用函数式权限检查 -->
<button v-if="hasPermission('user:export')" @click="handleExport">
导出用户
</button>
</div>
</template>

<script setup>
import { usePermission } from '@/hooks/usePermission'

const { hasPermission, hasRole } = usePermission()

// 方法权限检查
const handleExport = () => {
if (!hasPermission('user:export')) {
message.error('无权限操作')
return
}
// 执行导出逻辑
}
</script>

5.2 按钮级权限控制指令

javascript 复制代码
// permission.js - 权限指令
import { permissionStore } from '@/utils/permission'

export const permission = {
mounted(el, binding) {
const { value } = binding

if (value && value instanceof Array && value.length > 0) {
const permissions = value

// 检查权限
const hasPermission = permissions.some(perm => {
return permissionStore.hasPermission(perm)
})

// 没有权限则移除元素
if (!hasPermission) {
el.parentNode && el.parentNode.removeChild(el)
}
} else if (value && typeof value === 'string') {
// 单个权限检查
if (!permissionStore.hasPermission(value)) {
el.parentNode && el.parentNode.removeChild(el)
}
} else {
throw new Error('v-permission指令需要传入权限数组或字符串')
}
}
}

export const role = {
mounted(el, binding) {
const { value } = binding

if (value && value instanceof Array && value.length > 0) {
const roles = value

// 检查角色
const hasRole = roles.some(role => {
return permissionStore.hasRole(role)
})

// 没有角色则移除元素
if (!hasRole) {
el.parentNode && el.parentNode.removeChild(el)
}
} else if (value && typeof value === 'string') {
// 单个角色检查
if (!permissionStore.hasRole(value)) {
el.parentNode && el.parentNode.removeChild(el)
}
} else {
throw new Error('v-role指令需要传入角色数组或字符串')
}
}
}

// 在main.js中注册指令
import { permission, role } from '@/directives/permission'

const app = createApp(App)
app.directive('permission', permission)
app.directive('role', role)

六、RBAC扩展:数据权限实现

6.1 数据权限模型设计

sql 复制代码
-- 数据权限表
CREATE TABLE sys_data_permission (
id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT '主键ID',
user_id BIGINT COMMENT '用户ID',
role_id BIGINT COMMENT '角色ID',
permission_type VARCHAR(50) NOT NULL COMMENT '权限类型: DEPT, USER, ALL, CUSTOM',
data_scope VARCHAR(20) NOT NULL COMMENT '数据范围: ALL, DEPT, DEPT_AND_CHILD, SELF, CUSTOM',
resource_type VARCHAR(50) NOT NULL COMMENT '资源类型: USER, ORDER, PRODUCT...',
condition_sql VARCHAR(1000) COMMENT '自定义条件SQL',
description VARCHAR(500) COMMENT '描述',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
INDEX idx_user_id(user_id),
INDEX idx_role_id(role_id),
INDEX idx_resource_type(resource_type),
CHECK (
(user_id IS NOT NULL AND role_id IS NULL) OR
(user_id IS NULL AND role_id IS NOT NULL)
)
) COMMENT '数据权限表';

-- 数据权限规则表
CREATE TABLE sys_data_permission_rule (
id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT '主键ID',
data_permission_id BIGINT NOT NULL COMMENT '数据权限ID',
field_name VARCHAR(50) NOT NULL COMMENT '字段名',
operator VARCHAR(20) NOT NULL COMMENT '操作符: =, !=, >, <, IN, LIKE...',
field_value VARCHAR(500) COMMENT '字段值',
logical_operator VARCHAR(10) COMMENT '逻辑运算符: AND, OR',
sort INT DEFAULT 0 COMMENT '排序',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (data_permission_id) REFERENCES sys_data_permission(id) ON DELETE CASCADE
) COMMENT '数据权限规则表';

6.2 数据权限拦截器

java 复制代码
@Aspect
@Component
@Slf4j
public class DataPermissionAspect {

@Autowired
private DataPermissionService dataPermissionService;

@Pointcut("@annotation(com.example.rbac.annotation.DataScope)")
public void dataScopePointcut() {}

@Around("dataScopePointcut()")
public Object handleDataPermission(ProceedingJoinPoint joinPoint) throws Throwable {
try {
// 1. 获取数据权限注解
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Method method = signature.getMethod();
DataScope dataScope = method.getAnnotation(DataScope.class);

if (dataScope == null) {
return joinPoint.proceed();
}

// 2. 获取当前用户
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
String username = authentication.getName();

// 3. 获取用户的数据权限配置
List<DataPermission> permissions = dataPermissionService.getUserDataPermissions(username);

// 4. 构建SQL过滤条件
String sqlFilter = buildSqlFilter(dataScope, permissions);

// 5. 将过滤条件放入ThreadLocal
DataScopeContext.setFilter(sqlFilter);

// 6. 执行原方法
return joinPoint.proceed();

} finally {
// 7. 清理ThreadLocal
DataScopeContext.clear();
}
}

/**
* 构建SQL过滤条件
*/
private String buildSqlFilter(DataScope dataScope, List<DataPermission> permissions) {
if (permissions.isEmpty()) {
return "";
}

StringBuilder filter = new StringBuilder();
String resourceType = dataScope.value();

// 按优先级排序:用户级权限 > 角色级权限
permissions.sort((p1, p2) -> {
if (p1.getUserId() != null && p2.getUserId() == null) {
return -1; // 用户级权限优先
} else if (p1.getUserId() == null && p2.getUserId() != null) {
return 1;
}
return 0;
});

// 查找对当前资源类型有效的权限
List<DataPermission> effectivePermissions = permissions.stream()
.filter(p -> resourceType.equals(p.getResourceType()))
.collect(Collectors.toList());

if (effectivePermissions.isEmpty()) {
return "";
}

// 使用第一个有效的权限(因为已按优先级排序)
DataPermission permission = effectivePermissions.get(0);

// 根据数据范围构建条件
switch (permission.getDataScope()) {
case "ALL":
// 无限制
break;

case "DEPT":
filter.append(" AND dept_id = #{userDeptId}");
break;

case "DEPT_AND_CHILD":
filter.append(" AND dept_id IN (")
.append("SELECT id FROM sys_dept WHERE FIND_IN_SET(id, getDeptChildList(#{userDeptId}))")
.append(")");
break;

case "SELF":
filter.append(" AND create_user_id = #{userId}");
break;

case "CUSTOM":
if (StringUtils.isNotBlank(permission.getConditionSql())) {
filter.append(" AND ").append(permission.getConditionSql());
}
break;

default:
log.warn("未知的数据范围类型: {}", permission.getDataScope());
}

return filter.toString();
}
}

/**
* 数据权限上下文
*/
public class DataScopeContext {

private static final ThreadLocal<String> FILTER_CONTEXT = new ThreadLocal<>();

public static void setFilter(String filter) {
FILTER_CONTEXT.set(filter);
}

public static String getFilter() {
return FILTER_CONTEXT.get();
}

public static void clear() {
FILTER_CONTEXT.remove();
}
}

/**
* MyBatis拦截器,自动添加数据权限条件
*/
@Intercepts({
@Signature(type = Executor.class, method = "prepare", args = {Connection.class, Integer.class})
})
@Component
public class DataPermissionInterceptor implements Interceptor {

@Override
public Object intercept(Invocation invocation) throws Throwable {
String originalSql = getOriginalSql(invocation);
if (StringUtils.isBlank(originalSql)) {
return invocation.proceed();
}

// 获取数据权限过滤条件
String filter = DataScopeContext.getFilter();
if (StringUtils.isBlank(filter)) {
return invocation.proceed();
}

// 判断是否为查询语句
if (isSelectStatement(originalSql)) {
// 添加数据权限条件
String newSql = addDataPermissionCondition(originalSql, filter);
setNewSql(invocation, newSql);
}

return invocation.proceed();
}

private String getOriginalSql(Invocation invocation) {
// 获取原始SQL
MappedStatement mappedStatement = (MappedStatement) invocation.getArgs()[0];
Object parameter = invocation.getArgs()[1];
BoundSql boundSql = mappedStatement.getBoundSql(parameter);
return boundSql.getSql();
}

private boolean isSelectStatement(String sql) {
String trimmedSql = sql.trim().toUpperCase();
return trimmedSql.startsWith("SELECT");
}

private String addDataPermissionCondition(String sql, String filter) {
// 简单的SQL解析,在实际项目中可以使用SQL解析器
if (sql.toUpperCase().contains("WHERE")) {
return sql + " " + filter;
} else {
return sql + " WHERE 1=1 " + filter;
}
}

private void setNewSql(Invocation invocation, String newSql) {
// 修改SQL
MappedStatement mappedStatement = (MappedStatement) invocation.getArgs()[0];
Object parameter = invocation.getArgs()[1];
BoundSql boundSql = mappedStatement.getBoundSql(parameter);

// 使用反射修改SQL
try {
Field sqlField = BoundSql.class.getDeclaredField("sql");
sqlField.setAccessible(true);
sqlField.set(boundSql, newSql);
} catch (Exception e) {
throw new RuntimeException("修改SQL失败", e);
}
}
}

七、RBAC系统管理实现

7.1 角色管理服务

java 复制代码
@Service
@Slf4j
public class RoleService {

@Autowired
private RoleRepository roleRepository;

@Autowired
private PermissionRepository permissionRepository;

@Autowired
private UserRepository userRepository;

@Autowired
private MutexRoleRepository mutexRoleRepository;

/**
* 创建角色
*/
@Transactional
public Role createRole(CreateRoleRequest request) {
// 1. 检查角色编码是否已存在
if (roleRepository.existsByRoleCode(request.getRoleCode())) {
throw new BusinessException("角色编码已存在");
}

// 2. 创建角色
Role role = new Role();
role.setRoleCode(request.getRoleCode());
role.setRoleName(request.getRoleName());
role.setDescription(request.getDescription());
role.setStatus(request.getStatus());

roleRepository.save(role);

// 3. 分配权限
if (CollectionUtils.isNotEmpty(request.getPermissionIds())) {
assignPermissions(role.getId(), request.getPermissionIds());
}

return role;
}

/**
* 为角色分配权限
*/
@Transactional
public void assignPermissions(Long roleId, List<Long> permissionIds) {
// 1. 检查角色是否存在
Role role = roleRepository.findById(roleId)
.orElseThrow(() -> new NotFoundException("角色不存在"));

// 2. 检查所有权限是否存在
List<Permission> permissions = permissionRepository.findAllById(permissionIds);
if (permissions.size() != permissionIds.size()) {
throw new BusinessException("部分权限不存在");
}

// 3. 检查互斥权限
checkMutexPermissions(role, permissions);

// 4. 清除原有权限并分配新权限
role.getPermissions().clear();
role.getPermissions().addAll(permissions);

roleRepository.save(role);

log.info("为角色[{}]分配了{}个权限", role.getRoleName(), permissions.size());
}

/**
* 检查互斥权限
*/
private void checkMutexPermissions(Role role, List<Permission> permissions) {
// 获取所有互斥权限配置
List<MutexPermission> mutexPermissions = mutexPermissionRepository.findAll();

// 检查是否有互斥权限
for (Permission permission : permissions) {
for (MutexPermission mutex : mutexPermissions) {
if (mutex.getPermissionId1().equals(permission.getId()) ||
mutex.getPermissionId2().equals(permission.getId())) {

// 检查另一个互斥权限是否也在列表中
Long otherPermissionId = mutex.getPermissionId1().equals(permission.getId()) ?
mutex.getPermissionId2() : mutex.getPermissionId1();

boolean hasOtherPermission = permissions.stream()
.anyMatch(p -> p.getId().equals(otherPermissionId));

if (hasOtherPermission) {
Permission otherPermission = permissionRepository.findById(otherPermissionId)
.orElseThrow(() -> new NotFoundException("权限不存在"));

throw new BusinessException(String.format(
"权限[%s]和权限[%s]互斥,不能同时分配给同一角色",
permission.getPermissionName(),
otherPermission.getPermissionName()
));
}
}
}
}
}

/**
* 删除角色
*/
@Transactional
public void deleteRole(Long roleId) {
// 1. 检查角色是否被用户使用
long userCount = userRepository.countByRoleId(roleId);
if (userCount > 0) {
throw new BusinessException("该角色已被用户使用,无法删除");
}

// 2. 检查是否为系统内置角色
Role role = roleRepository.findById(roleId)
.orElseThrow(() -> new NotFoundException("角色不存在"));

if (isSystemRole(role.getRoleCode())) {
throw new BusinessException("系统内置角色不可删除");
}

// 3. 删除角色
roleRepository.deleteById(roleId);

log.info("删除角色[{}]", role.getRoleName());
}

/**
* 检查是否为系统内置角色
*/
private boolean isSystemRole(String roleCode) {
return Arrays.asList("super_admin", "admin", "auditor").contains(roleCode);
}

/**
* 获取角色的完整权限树(包括继承的权限)
*/
public List<Permission> getRolePermissionsWithInheritance(Long roleId) {
Role role = roleRepository.findByIdWithPermissions(roleId)
.orElseThrow(() -> new NotFoundException("角色不存在"));

Set<Permission> allPermissions = new HashSet<>();

// 添加直接分配的权限
allPermissions.addAll(role.getPermissions());

// 添加继承的权限
getAllInheritedPermissions(role, allPermissions);

// 转换为树形结构
return buildPermissionTree(new ArrayList<>(allPermissions));
}

/**
* 获取所有继承的权限
*/
private void getAllInheritedPermissions(Role role, Set<Permission> allPermissions) {
// 获取父角色
Set<Role> parentRoles = role.getParentRoles();

for (Role parentRole : parentRoles) {
// 添加父角色的权限
allPermissions.addAll(parentRole.getPermissions());

// 递归获取祖父角色的权限
getAllInheritedPermissions(parentRole, allPermissions);
}
}

/**
* 构建权限树
*/
private List<Permission> buildPermissionTree(List<Permission> permissions) {
// 构建权限ID到权限对象的映射
Map<Long, Permission> permissionMap = new HashMap<>();
permissions.forEach(p -> permissionMap.put(p.getId(), p));

// 构建权限树
List<Permission> rootPermissions = new ArrayList<>();

for (Permission permission : permissions) {
if (permission.getParentId() == 0) {
rootPermissions.add(permission);
} else {
Permission parent = permissionMap.get(permission.getParentId());
if (parent != null) {
parent.getChildren().add(permission);
}
}
}

// 按sort排序
rootPermissions.sort(Comparator.comparingInt(Permission::getSort));
rootPermissions.forEach(p -> sortChildren(p.getChildren()));

return rootPermissions;
}

private void sortChildren(List<Permission> children) {
if (children == null || children.isEmpty()) {
return;
}
children.sort(Comparator.comparingInt(Permission::getSort));
children.forEach(p -> sortChildren(p.getChildren()));
}
}

7.2 用户角色分配服务

java 复制代码
@Service
@Slf4j
public class UserRoleService {

@Autowired
private UserRepository userRepository;

@Autowired
private RoleRepository roleRepository;

@Autowired
private MutexRoleRepository mutexRoleRepository;

/**
* 为用户分配角色
*/
@Transactional
public void assignRolesToUser(Long userId, List<Long> roleIds) {
// 1. 检查用户是否存在
User user = userRepository.findById(userId)
.orElseThrow(() -> new NotFoundException("用户不存在"));

// 2. 检查所有角色是否存在
List<Role> roles = roleRepository.findAllById(roleIds);
if (roles.size() != roleIds.size()) {
throw new BusinessException("部分角色不存在");
}

// 3. 检查静态职责分离(SSD)
checkStaticSeparationOfDuties(user, roles);

// 4. 分配角色
Set<Role> currentRoles = user.getRoles();
currentRoles.clear();
currentRoles.addAll(roles);

userRepository.save(user);

log.info("为用户[{}]分配了{}个角色", user.getUsername(), roles.size());
}

/**
* 检查静态职责分离
*/
private void checkStaticSeparationOfDuties(User user, List<Role> newRoles) {
// 获取所有互斥角色配置
List<MutexRole> mutexRoles = mutexRoleRepository.findAll();

// 检查新分配的角色之间是否有互斥
for (int i = 0; i < newRoles.size(); i++) {
for (int j = i + 1; j < newRoles.size(); j++) {
Role role1 = newRoles.get(i);
Role role2 = newRoles.get(j);

boolean isMutex = mutexRoles.stream()
.anyMatch(mr -> (mr.getRole1().getId().equals(role1.getId()) &&
mr.getRole2().getId().equals(role2.getId())) ||
(mr.getRole1().getId().equals(role2.getId()) &&
mr.getRole2().getId().equals(role1.getId())));

if (isMutex) {
throw new BusinessException(String.format(
"角色[%s]和角色[%s]互斥,不能同时分配给同一用户",
role1.getRoleName(),
role2.getRoleName()
));
}
}
}

// 检查新角色与现有角色是否有互斥
Set<Role> existingRoles = user.getRoles();
for (Role newRole : newRoles) {
for (Role existingRole : existingRoles) {
boolean isMutex = mutexRoles.stream()
.anyMatch(mr -> (mr.getRole1().getId().equals(newRole.getId()) &&
mr.getRole2().getId().equals(existingRole.getId())) ||
(mr.getRole1().getId().equals(existingRole.getId()) &&
mr.getRole2().getId().equals(newRole.getId())));

if (isMutex) {
throw new BusinessException(String.format(
"角色[%s]和角色[%s]互斥,不能同时分配给同一用户",
newRole.getRoleName(),
existingRole.getRoleName()
));
}
}
}
}

/**
* 获取用户的角色和权限
*/
public UserRolePermissionDTO getUserRolesAndPermissions(Long userId) {
User user = userRepository.findByIdWithRoles(userId)
.orElseThrow(() -> new NotFoundException("用户不存在"));

UserRolePermissionDTO dto = new UserRolePermissionDTO();
dto.setUserId(userId);
dto.setUsername(user.getUsername());

// 获取直接分配的角色
Set<Role> directRoles = user.getRoles();
dto.setDirectRoles(directRoles.stream()
.map(this::convertToRoleDTO)
.collect(Collectors.toSet()));

// 获取所有角色(包括继承的角色)
Set<Role> allRoles = getAllInheritedRoles(directRoles);
dto.setAllRoles(allRoles.stream()
.map(this::convertToRoleDTO)
.collect(Collectors.toSet()));

// 获取所有权限
Set<Permission> allPermissions = getAllPermissions(allRoles);
dto.setPermissions(allPermissions.stream()
.map(this::convertToPermissionDTO)
.collect(Collectors.toSet()));

return dto;
}

/**
* 获取所有继承的角色
*/
private Set<Role> getAllInheritedRoles(Set<Role> directRoles) {
Set<Role> allRoles = new HashSet<>(directRoles);
Queue<Role> queue = new LinkedList<>(directRoles);

while (!queue.isEmpty()) {
Role currentRole = queue.poll();

// 获取当前角色的所有子角色
Set<Role> childRoles = roleRepository.findChildRoles(currentRole.getId());

for (Role childRole : childRoles) {
if (!allRoles.contains(childRole)) {
allRoles.add(childRole);
queue.offer(childRole);
}
}
}

return allRoles;
}

/**
* 获取所有权限
*/
private Set<Permission> getAllPermissions(Set<Role> roles) {
Set<Permission> allPermissions = new HashSet<>();

for (Role role : roles) {
allPermissions.addAll(role.getPermissions());
}

return allPermissions;
}
}

八、高级功能:动态权限与ABAC集成

8.1 动态权限规则引擎

java 复制代码
@Component
public class DynamicPermissionEngine {

@Autowired
private RuleEngine ruleEngine;

/**
* 检查动态权限
*/
public boolean checkDynamicPermission(User user, Resource resource, String action, Map<String, Object> context) {
// 1. 加载权限规则
List<PermissionRule> rules = loadPermissionRules(user, resource, action);

// 2. 按优先级排序
rules.sort(Comparator.comparingInt(PermissionRule::getPriority).reversed());

// 3. 评估规则
for (PermissionRule rule : rules) {
if (evaluateRule(rule, user, resource, action, context)) {
return rule.getEffect() == RuleEffect.ALLOW;
}
}

// 4. 默认拒绝
return false;
}

/**
* 加载权限规则
*/
private List<PermissionRule> loadPermissionRules(User user, Resource resource, String action) {
// 从数据库或缓存加载规则
// 可以根据用户角色、资源类型、操作类型等条件过滤规则
return permissionRuleRepository.findByResourceTypeAndAction(
resource.getResourceType(), action);
}

/**
* 评估规则
*/
private boolean evaluateRule(PermissionRule rule, User user, Resource resource,
String action, Map<String, Object> context) {
try {
// 构建规则上下文
Map<String, Object> ruleContext = new HashMap<>();
ruleContext.put("user", user);
ruleContext.put("resource", resource);
ruleContext.put("action", action);
ruleContext.put("context", context);

// 使用规则引擎评估
return ruleEngine.evaluate(rule.getCondition(), ruleContext);
} catch (Exception e) {
log.error("评估权限规则失败: {}", rule.getId(), e);
return false;
}
}
}

/**
* 权限规则实体
*/
@Entity
@Table(name = "sys_permission_rule")
@Data
public class PermissionRule {

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

@Column(nullable = false, length = 100)
private String ruleName;

@Column(length = 500)
private String description;

@Column(nullable = false, length = 50)
private String resourceType;

@Column(nullable = false, length = 50)
private String action;

@Column(nullable = false, length = 1000)
private String condition; // 规则条件表达式,如:user.department == resource.ownerDepartment

@Enumerated(EnumType.STRING)
@Column(nullable = false, length = 10)
private RuleEffect effect; // ALLOW, DENY

@Column(nullable = false)
private Integer priority = 0;

@Column(nullable = false)
private Boolean enabled = true;
}

/**
* ABAC权限检查示例
*/
@Service
public class ABACPermissionService {

@Autowired
private DynamicPermissionEngine permissionEngine;

/**
* ABAC权限检查:基于属性的访问控制
*/
public boolean checkABACPermission(Long userId, String resourceType, Long resourceId, String action) {
// 1. 获取用户信息
User user = userService.getUserById(userId);

// 2. 获取资源信息
Resource resource = resourceService.getResource(resourceType, resourceId);

// 3. 构建上下文
Map<String, Object> context = new HashMap<>();
context.put("time", LocalDateTime.now());
context.put("ip", getCurrentUserIp());
context.put("location", getCurrentUserLocation());

// 4. 检查动态权限
return permissionEngine.checkDynamicPermission(user, resource, action, context);
}

/**
* 复杂权限规则示例:
* 1. 用户只能在工作时间(9:00-18:00)访问系统
* 2. 用户只能访问自己部门的文档
* 3. 财务人员只能查看金额小于10000的订单
* 4. 项目经理可以查看项目成员的所有文档
*/

/**
* 检查文档访问权限
*/
public boolean checkDocumentAccess(Long userId, Long documentId) {
User user = userService.getUserById(userId);
Document document = documentService.getDocument(documentId);

Map<String, Object> context = new HashMap<>();
context.put("operationTime", LocalDateTime.now());

// 规则1:检查是否在工作时间
LocalTime currentTime = LocalTime.now();
if (currentTime.isBefore(LocalTime.of(9, 0)) ||
currentTime.isAfter(LocalTime.of(18, 0))) {
return false; // 非工作时间不允许访问
}

// 规则2:检查部门权限
if (!user.getDepartmentId().equals(document.getDepartmentId())) {
// 规则3:如果是项目经理,可以访问项目成员的文档
if (!user.getRoles().contains("PROJECT_MANAGER")) {
return false;
}

// 检查是否是项目成员
boolean isProjectMember = projectService.isProjectMember(
user.getId(), document.getProjectId());
if (!isProjectMember) {
return false;
}
}

// 规则4:敏感文档需要额外审批
if (document.isSensitive()) {
boolean hasApproval = approvalService.hasDocumentApproval(userId, documentId);
if (!hasApproval) {
return false;
}
}

return true;
}
}

九、性能优化与最佳实践

9.1 缓存策略

java 复制代码
@Configuration
@EnableCaching
public class CacheConfig {

@Bean
public CacheManager cacheManager(RedisConnectionFactory connectionFactory) {
RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofMinutes(30))
.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer()))
.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer()));

return RedisCacheManager.builder(connectionFactory)
.cacheDefaults(config)
.withInitialCacheConfigurations(getCacheConfigurations())
.transactionAware()
.build();
}

private Map<String, RedisCacheConfiguration> getCacheConfigurations() {
Map<String, RedisCacheConfiguration> configMap = new HashMap<>();

// 用户权限缓存,设置较短时间(5分钟)
configMap.put("userPermissions",
RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofMinutes(5))
.disableCachingNullValues());

// 角色权限缓存,设置较长时间(1小时)
configMap.put("rolePermissions",
RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofHours(1))
.disableCachingNullValues());

// 权限树缓存,设置长时间(24小时)
configMap.put("permissionTree",
RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofHours(24))
.disableCachingNullValues());

return configMap;
}
}

@Service
@Slf4j
public class PermissionCacheService {

@Autowired
private CacheManager cacheManager;

@Autowired
private PermissionService permissionService;

/**
* 获取用户权限(带缓存)
*/
@Cacheable(value = "userPermissions", key = "#userId")
public Set<String> getUserPermissions(Long userId) {
log.info("从数据库加载用户[{}]的权限", userId);
return permissionService.getUserPermissions(userId);
}

/**
* 清除用户权限缓存
*/
@CacheEvict(value = "userPermissions", key = "#userId")
public void clearUserPermissions(Long userId) {
log.info("清除用户[{}]的权限缓存", userId);
}

/**
* 获取角色权限树(带缓存)
*/
@Cacheable(value = "permissionTree", key = "#roleId")
public List<PermissionTreeVO> getRolePermissionTree(Long roleId) {
log.info("从数据库加载角色[{}]的权限树", roleId);
return permissionService.getRolePermissionTree(roleId);
}

/**
* 批量清除缓存
*/
public void clearAllCache() {
log.info("清除所有权限缓存");

Cache userPermissionsCache = cacheManager.getCache("userPermissions");
if (userPermissionsCache != null) {
userPermissionsCache.clear();
}

Cache rolePermissionsCache = cacheManager.getCache("rolePermissions");
if (rolePermissionsCache != null) {
rolePermissionsCache.clear();
}

Cache permissionTreeCache = cacheManager.getCache("permissionTree");
if (permissionTreeCache != null) {
permissionTreeCache.clear();
}
}

/**
* 权限变更时的事件监听
*/
@EventListener
public void handlePermissionChangeEvent(PermissionChangeEvent event) {
log.info("处理权限变更事件: {}", event.getEventType());

switch (event.getEventType()) {
case USER_ROLE_CHANGED:
clearUserPermissions(event.getUserId());
break;
case ROLE_PERMISSION_CHANGED:
clearRolePermissions(event.getRoleId());
clearPermissionTree(event.getRoleId());
break;
case PERMISSION_UPDATED:
clearAllCache();
break;
}
}
}

// 权限变更事件
@Data
@AllArgsConstructor
public class PermissionChangeEvent {
private EventType eventType;
private Long userId;
private Long roleId;

public enum EventType {
USER_ROLE_CHANGED,
ROLE_PERMISSION_CHANGED,
PERMISSION_UPDATED
}
}

9.2 批量操作优化

java 复制代码
@Service
@Slf4j
public class BatchPermissionService {

@Autowired
private RoleRepository roleRepository;

@Autowired
private PermissionRepository permissionRepository;

@Autowired
private JdbcTemplate jdbcTemplate;

/**
* 批量分配权限(使用批量插入)
*/
@Transactional
public void batchAssignPermissions(Long roleId, List<Long> permissionIds) {
if (CollectionUtils.isEmpty(permissionIds)) {
return;
}

// 使用批量插入提高性能
String sql = "INSERT INTO sys_role_permission (role_id, permission_id, created_at) VALUES (?, ?, NOW())";

jdbcTemplate.batchUpdate(sql, new BatchPreparedStatementSetter() {
@Override
public void setValues(PreparedStatement ps, int i) throws SQLException {
ps.setLong(1, roleId);
ps.setLong(2, permissionIds.get(i));
}

@Override
public int getBatchSize() {
return permissionIds.size();
}
});

// 清除相关缓存
permissionCacheService.clearRolePermissions(roleId);
permissionCacheService.clearPermissionTree(roleId);
}

/**
* 批量删除权限
*/
@Transactional
public void batchRemovePermissions(Long roleId, List<Long> permissionIds) {
if (CollectionUtils.isEmpty(permissionIds)) {
return;
}

// 使用IN查询提高性能
String sql = "DELETE FROM sys_role_permission WHERE role_id = ? AND permission_id IN (?)";

// 将List转换为逗号分隔的字符串
String permissionIdStr = permissionIds.stream()
.map(String::valueOf)
.collect(Collectors.joining(","));

jdbcTemplate.update(sql, roleId, permissionIdStr);

// 清除相关缓存
permissionCacheService.clearRolePermissions(roleId);
permissionCacheService.clearPermissionTree(roleId);
}

/**
* 批量用户角色分配
*/
@Transactional
public void batchAssignRolesToUsers(List<Long> userIds, List<Long> roleIds) {
if (CollectionUtils.isEmpty(userIds) || CollectionUtils.isEmpty(roleIds)) {
return;
}

// 使用批量插入
String sql = "INSERT INTO sys_user_role (user_id, role_id, created_at) VALUES (?, ?, NOW())";

List<Object[]> batchArgs = new ArrayList<>();
for (Long userId : userIds) {
for (Long roleId : roleIds) {
batchArgs.add(new Object[]{userId, roleId});
}
}

jdbcTemplate.batchUpdate(sql, batchArgs);

// 清除用户权限缓存
userIds.forEach(permissionCacheService::clearUserPermissions);
}
}

9.3 最佳实践总结

1. 数据库设计优化
  • 建立合适的索引:
sql 复制代码
CREATE INDEX idx_user_role_user_id ON sys_user_role(user_id);
CREATE INDEX idx_user_role_role_id ON sys_user_role(role_id);
CREATE INDEX idx_role_permission_role_id ON sys_role_permission(role_id);
CREATE INDEX idx_role_permission_permission_id ON sys_role_permission(permission_id);
  • 定期清理历史数据:
sql 复制代码
-- 清理无效的用户角色关系
DELETE ur FROM sys_user_role ur
LEFT JOIN sys_user u ON ur.user_id = u.id
LEFT JOIN sys_role r ON ur.role_id = r.id
WHERE u.id IS NULL OR r.id IS NULL OR u.status = 0 OR r.status = 0;
2. 性能优化策略
  • 缓存策略

  • 用户权限缓存:5-10分钟

  • 角色权限缓存:30-60分钟

  • 权限树缓存:24小时

  • 数据字典缓存:永久(变更时更新)

  • 懒加载优化

java 复制代码
@Entity
public class Role {
@ManyToMany(fetch = FetchType.LAZY)
@JoinTable(name = "sys_role_permission")
@BatchSize(size = 100)
private Set<Permission> permissions;
}
  • 查询优化
java 复制代码
// 使用投影查询减少数据传输
public interface RolePermissionProjection {
Long getRoleId();
String getRoleCode();
String getPermissionCode();
}

@Query("SELECT r.id as roleId, r.roleCode, p.permissionCode " +
"FROM Role r JOIN r.permissions p " +
"WHERE r.status = 1 AND p.status = 1")
List<RolePermissionProjection> findAllActiveRolePermissions();
3. 安全建议
  • 最小权限原则:只授予必要的最小权限
  • 定期审计:定期检查权限分配情况
  • 权限回收:用户离职或转岗时及时回收权限
  • 密码策略:强制使用强密码并定期更换
  • 会话管理:设置合理的会话超时时间
4. 监控与告警
java 复制代码
@Component
@Slf4j
public class PermissionMonitor {

@Scheduled(cron = "0 0 2 * * ?") // 每天凌晨2点执行
public void checkPermissionAnomalies() {
log.info("开始权限异常检查...");

// 检查超级管理员数量
long superAdminCount = userService.countUsersByRole("super_admin");
if (superAdminCount > 3) {
sendAlert("超级管理员数量异常: " + superAdminCount);
}

// 检查未使用的权限
List<Permission> unusedPermissions = permissionService.findUnusedPermissions();
if (!unusedPermissions.isEmpty()) {
log.warn("发现{}个未使用的权限", unusedPermissions.size());
}

// 检查权限分配异常
List<UserRoleStatistic> statistics = userService.getUserRoleStatistics();
statistics.stream()
.filter(stat -> stat.getRoleCount() > 10)
.forEach(stat -> {
sendAlert(String.format("用户[%s]拥有过多角色: %d个",
stat.getUsername(), stat.getRoleCount()));
});
}

private void sendAlert(String message) {
// 发送告警通知
log.error("权限异常告警: {}", message);
notificationService.sendAlert("权限异常告警", message);
}
}

十、部署与维护

10.1 Docker部署配置

dockerfile 复制代码
# Dockerfile
FROM openjdk:11-jre-slim

# 设置时区
ENV TZ=Asia/Shanghai
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone

# 创建应用目录
RUN mkdir -p /app
WORKDIR /app

# 复制应用
COPY target/rbac-system.jar app.jar
COPY config/application-prod.yml application.yml

# 暴露端口
EXPOSE 8080

# 运行应用
ENTRYPOINT ["java", "-jar", "app.jar", "--spring.config.location=application.yml"]
yaml 复制代码
# docker-compose.yml
version: '3.8'

services:
rbac-mysql:
image: mysql:8.0
container_name: rbac-mysql
environment:
MYSQL_ROOT_PASSWORD: ${DB_ROOT_PASSWORD}
MYSQL_DATABASE: rbac_system
MYSQL_USER: ${DB_USER}
MYSQL_PASSWORD: ${DB_PASSWORD}
volumes:
- mysql-data:/var/lib/mysql
- ./init.sql:/docker-entrypoint-initdb.d/init.sql
ports:
- "3306:3306"
networks:
- rbac-network
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "localhost
相关推荐
奔跑的小十一2 小时前
ShardingSphere-JDBC 开发手册
java·数据库
lkbhua莱克瓦242 小时前
基础-MySQL概述
java·开发语言·数据库·笔记·mysql
小安运维日记2 小时前
RHCA - DO374 | Day09:自定义内容集和执行环境
linux·运维·服务器·系统架构·ansible·改行学it
开发者导航2 小时前
【开发者导航】完全开源免费且可自托管的私有云盘
网络·开源
月明长歌2 小时前
【码道初阶】Leetcode136:只出现一次的数字:异或一把梭 vs HashMap 计数(两种解法完整复盘)
java·数据结构·算法·leetcode·哈希算法
Swift社区2 小时前
LeetCode 456 - 132 模式
java·算法·leetcode
网安INF2 小时前
典型网络攻击分析:ARP欺骗与TCP劫持
网络·网络协议·tcp/ip·安全·网络安全
VekiSon2 小时前
Linux网络编程——网络数据封装与 HTTP 协议
网络·网络协议·http
2501_938810112 小时前
动态IP与短效IP的关系
服务器·网络协议·tcp/ip