控制权限系列之(2)手把手教你使用基于角色的权限控制

前一篇已经分析了多种权限模型,其中比较常用的是基于角色的权限控制。

基于角色的权限控制

表设计:

复制代码
用户表
用户--角色关系表
角色表
角色--菜单关系表
菜单表

权限标识格式:

复制代码
格式:xxx:xxx:xxx(模块:资源:操作) 三段式

权限架构的分层结构:

复制代码
应用层 (@RequirePermission注解)
    ↓
权限验证层 (PermissionValidator)
    ↓
权限上下文层 (PermissionContext - ThreadLocal)
    ↓
权限提供层 (PermissionProvider - 数据库/缓存)
    ↓
权限存储层 (PermissionStorage - Redis/Memory)

权限架构接口:

权限提供者接口 (PermissionProvider)

复制代码
 1 public interface PermissionProvider {
 2     // 获取用户的所有权限标识
 3     Set<String> getUserPermissions(Object userId);
 4     
 5     // 获取用户角色列表
 6     Set<String> getUserRoles(Object userId);
 7     
 8     // 检查用户是否为超级管理员
 9     boolean isSuperAdmin(Object userId);
10     
11     // 刷新用户权限缓存
12     void refreshUserPermissions(Object userId);
13 }
  • userId 使用 Object 类型,支持 String、Long 等不同ID类型
  • 支持超级管理员逻辑(拥有所有权限)
  • 提供刷新接口,支持权限变更后更新缓存

权限存储接口 (PermissionStorage)

复制代码
 1 public interface PermissionStorage {
 2     // 存储用户权限信息
 3     void storeUserInfo(String token, UserInfo userInfo);
 4     
 5     // 获取用户权限信息
 6     UserInfo getUserInfo(String token);
 7     
 8     // 删除用户权限信息
 9     void removeUserInfo(String token);
10     
11     // 刷新用户权限信息
12     void refreshUserInfo(String token, UserInfo userInfo);
13 }
  • 基于Token存储,支持分布式场景
  • 支持多种存储实现(Redis、Memory、数据库等)
  • 提供刷新接口,支持权限实时更新

权限匹配器接口 (PermissionMatcher)

复制代码
 1 public interface PermissionMatcher {
 2     // 匹配权限
 3     boolean match(String requiredPermission, Set<String> userPermissions);
 4     
 5     // 批量匹配(全部满足)
 6     boolean matchAll(Set<String> requiredPermissions, Set<String> userPermissions);
 7     
 8     // 批量匹配(任意一个满足)
 9     boolean matchAny(Set<String> requiredPermissions, Set<String> userPermissions);
10 }
  • 支持多种匹配策略(精确、通配符、正则等)
  • 支持批量匹配(AND/OR逻辑)
  • 可扩展自定义匹配规则

用户信息

复制代码
1 public class UserInfo {
2     private Object userId;                    // 用户ID
3     private String username;                  // 用户名
4     private Set<String> permissions;          // 权限集合
5     private Set<String> roles;                // 角色集合
6     private boolean superAdmin;               // 是否超级管理员
7     private Map<String, Object> attributes;   // 扩展属性
8 }
  • 使用 Object 类型支持不同ID类型
  • 提供扩展属性,支持业务字段存储
  • 轻量级设计,便于序列化和传输

权限上下文 (PermissionContext)

复制代码
 1 public class PermissionContext {
 2     private static final ThreadLocal<UserInfo> USER_INFO_HOLDER = new TransmittableThreadLocal<>();
 4     
 5     public static void set(UserInfo userInfo);
 6     public static UserInfo get();
 7     public static void clear();
 8     
 9     public static boolean hasPermission(String permission);
10     public static Set<String> getPermissions();
11 }
  • 使用 TransmittableThreadLocal 支持线程池传递
  • 提供便捷方法,简化权限判断
  • 请求结束后自动清理,避免内存泄漏

权限验证器接口 (PermissionValidator)

复制代码
 1 public interface PermissionValidator {
 2     // 验证单个权限
 3     boolean hasPermission(String permission);
 4     
 5     // 验证所有权限(AND)
 6     boolean hasAllPermissions(Set<String> permissions);
 7     
 8     // 验证任意权限(OR)
 9     boolean hasAnyPermission(Set<String> permissions);
10     
11     // 验证角色
12     boolean hasRole(String role);
13     
14     // 获取当前用户上下文
15     UserContext getCurrentUser();
16 }
  • 提供多种验证方式(单个、全部、任意)
  • 支持角色验证
  • 统一权限验证入口

实现策略

数据库权限获取

  1. 通过JdbcTemplate执行SQL查询
  2. SQL可配置化(支持不同数据库表结构)
  3. 支持超级管理员特殊处理
  4. 支持权限缓存
复制代码
 1 @Component
 2 public class DatabasePermissionProvider implements PermissionProvider {
 3     
 4     @Override
 5     public Set<String> getUserPermissions(Object userId) {
 6         // 1. 检查是否为超级管理员
 7         if (isSuperAdmin(userId)) {
 8             return getAllPermissions(); // 返回所有权限
 9         }
10         
11         // 2. 查询用户权限(通过用户-角色-菜单关联)
12         List<String> permsList =userService.getPermissions();19         
20         // 3. 返回权限集合
21         return permsList;
27     }
28 }

抽象权限提供者 (AbstractPermissionProvider)

  • 提供模板方法,定义权限获取流程
  • 子类只需实现具体查询逻辑
  • 统一处理超级管理员逻辑
复制代码
 1 public abstract class AbstractPermissionProvider implements PermissionProvider {
 2     
 3     @Override
 4     public Set<String> getUserPermissions(Object userId) {
 5         // 方法:统一处理超管
 6         if (isSuperAdmin(userId)) {
 7             return getAllPermissions();
 8         }
 9         return doGetUserPermissions(userId);
10     }
11     
12     // 子类实现:获取所有权限
13     protected abstract Set<String> getAllPermissions();
14     
15     // 子类实现:查询用户权限
16     protected abstract Set<String> doGetUserPermissions(Object userId);
17 }

Redis存储 (RedisPermissionStorage)

  1. 使用Redis存储用户信息(JSON序列化)
  2. 支持过期时间配置
  3. 支持Key前缀配置
  4. 异常处理和日志记录
复制代码
 1 public class RedisPermissionStorage implements PermissionStorage {
 2     
 3     @Override
 4     public void storeUserInfo(String token, UserInfo userInfo) { 7         redisTemplate.opsForValue().set(key, value, expireSeconds, TimeUnit.SECONDS);
 8     }
 9     
10     @Override
11     public UserInfo getUserInfo(String token) {14         return value;
15     }
16 }

通配符匹配器 (WildcardPermissionMatcher)

  • 支持 *? 通配符
  • 例如:sys:user:* 匹配 sys:user:savesys:user:update
  • 使用Spring的 PatternMatchUtils.simpleMatch()

权限过滤器实现

  1. 请求头提取Token
  2. 从Storage获取用户信息(如不存在,从Provider加载)
  3. 设置到PermissionContext
  4. 请求结束后清理上下文
复制代码
过滤器流程:

请求到达
    ↓
检查排除路径(登录、公开接口等)
    ↓
提取Token
    ↓
从Storage获取用户信息
    ↓(如果不存在)
从Provider加载用户信息 → 存储到Storage
    ↓
设置到PermissionContext
    ↓
继续请求处理
    ↓
清理PermissionContext

权限注解 (@RequirePermission)

  • 支持单个权限、多个权限(AND/OR)
  • 支持角色验证
  • 支持自定义错误消息
复制代码
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface RequirePermission {
    String value() default "";           // 单个权限
    String[] all() default {};            // 全部权限(AND)
    String[] any() default {};            // 任意权限(OR)
    String role() default "";              // 单个角色
    String message() default "权限不足";   // 错误消息
}

AOP切面实现

  1. 拦截 @RequirePermission 注解的方法
  2. 从PermissionContext获取用户信息
  3. 调用PermissionValidator验证权限
  4. 验证失败抛出异常
复制代码
 1 @Aspect
 2 public class PermissionAspect {
 3     
 4     @Before("@annotation(RequirePermission)")
 5     public void checkPermission(JoinPoint joinPoint) {
 6         RequirePermission annotation = getAnnotation(joinPoint);
 7         
 8         // 验证权限
 9         if (!permissionValidator.hasPermission(annotation.value())) {
10             throw new PermissionDeniedException(annotation.message());
11         }
12     }
13 }

与SpringBoot项目集成

配置类设计

  • 自动配置各个组件
  • 支持条件装配(@ConditionalOnMissingBean)
  • 提供默认实现
复制代码
 1 @Configuration
 2 @EnableConfigurationProperties(PermissionProperties.class)
 3 public class PermissionAutoConfiguration {
 4     
 5     @Bean
 6     @ConditionalOnMissingBean
 7     public PermissionMatcher permissionMatcher() {
 8         return new WildcardPermissionMatcher(); 
 9     }
10     
11     @Bean
12     @ConditionalOnMissingBean
13     public PermissionValidator permissionValidator(PermissionMatcher matcher) {
14         return new DefaultPermissionValidator(matcher);
15     }
16     
17     @Bean
18     @ConditionalOnMissingBean
19     public PermissionFilter permissionFilter(...) {
20         return new PermissionFilter(...);
21     }
22 }

使用示例:

复制代码
 1 @RestController
 2 @RequestMapping("/api/user")
 3 public class UserController {
 4     
 5     // 单个权限验证
 6     @GetMapping("/list")
 7     @RequirePermission("sys:user:list")
 8     public ResponseEntity<List<User>> list() {
 9         return ResponseEntity.ok(userService.list());
10     }
11     
12     // 多个权限(全部满足)
13     @PutMapping("/{id}")
14     @RequirePermission(all = {"sys:user:update", "sys:user:edit"})
15     public ResponseEntity<Void> update(@PathVariable Long id, @RequestBody User user) {
16         userService.update(id, user);
17         return ResponseEntity.ok().build();
18     }
19     
20     // 多个权限(任意一个)
21     @DeleteMapping("/{id}")
22     @RequirePermission(any = {"sys:user:delete", "sys:user:remove"})
23     public ResponseEntity<Void> delete(@PathVariable Long id) {
24         userService.delete(id);
25         return ResponseEntity.ok().build();
26     }
27     
28     // 角色验证
29     @GetMapping("/admin")
30     @RequirePermission(role = "ADMIN")
31     public ResponseEntity<String> adminOnly() {
32         return ResponseEntity.ok("Admin only");
33     }
34 }

代码逻辑使用:

复制代码
 1 @Service
 2 public class UserService {
 3     
 4     @Autowired
 5     private PermissionValidator permissionValidator;
 6     
 7     public void deleteUser(Long userId) {
 8         
 9         if (!permissionValidator.hasPermission("sys:user:delete")) {
10             throw new PermissionDeniedException("无删除权限");
11         }
12         
13         userRepository.delete(userId);
14     }
15     
16     public UserContext getCurrentUser() {
17         return permissionValidator.getCurrentUser();
18     }
19 }

总结:

  1. 接口抽象:所有核心功能都通过接口定义,便于扩展
  2. 分层设计:Provider → Storage → Matcher → Validator,职责清晰
  3. 上下文管理:使用ThreadLocal,支持异步场景
  4. 配置化:SQL、路径等可配置,适应不同项目
  5. 默认实现:提供常用实现,开箱即用

按照以上设计思路,可实现一个通用、灵活、易用的权限控制框架。

相关推荐
Cobyte19 分钟前
AI全栈实战:使用 Python+LangChain+Vue3 构建一个 LLM 聊天应用
前端·后端·aigc
程序员侠客行1 小时前
Mybatis连接池实现及池化模式
java·后端·架构·mybatis
Honmaple1 小时前
QMD (Quarto Markdown) 搭建与使用指南
后端
PP东2 小时前
Flowable学习(二)——Flowable概念学习
java·后端·学习·flowable
invicinble2 小时前
springboot的核心实现机制原理
java·spring boot·后端
全栈老石2 小时前
Python 异步生存手册:给被 JS async/await 宠坏的全栈工程师
后端·python
space62123272 小时前
在SpringBoot项目中集成MongoDB
spring boot·后端·mongodb
Tony Bai3 小时前
再见,丑陋的 container/heap!Go 泛型堆 heap/v2 提案解析
开发语言·后端·golang
寻找奶酪的mouse3 小时前
30岁技术人对职业和生活的思考
前端·后端·年终总结
梦想很大很大4 小时前
使用 Go + Gin + Fx 构建工程化后端服务模板(gin-app 实践)
前端·后端·go