🔐 基于自定义注解与AOP的精细化权限控制

🌟 前言:权限控制的演进之路

在分布式系统开发中,接口权限控制是保障系统安全的重要环节。传统基于数据库字段查询的权限控制方案存在几个明显缺陷:

  1. 性能瓶颈:每次请求都需要查询数据库获取角色信息
  2. 代码冗余:相同的校验逻辑分散在各个Controller中
  3. 维护困难:权限规则变更需要修改多处业务代码

以用户管理系统为例,我们需要区分管理员(admin)和普通用户(user)的权限。传统实现方式如下:

// 传统实现方式的典型代码
@GetMapping("/admin/users")
public List<User> getAllUsers(HttpServletRequest request) {
// 每次都需要查询数据库校验权限
User loginUser = userService.getLoginUser(request);
if (!"admin".equals(loginUser.getUserRole())) {
throw new RuntimeException("无权限访问");
}
return userService.list();
}

本文将介绍如何通过自定义注解+Spring AOP实现声明式的权限控制,让权限校验逻辑与业务代码完美解耦。

🧩 核心组件设计

1. 自定义注解设计原理

Java注解本质上是接口的扩展,通过@interface关键字定义。我们设计的@AuthCheck注解包含以下关键特性:

  • @Target(ElementType.METHOD):限定注解只能用于方法
  • @Retention(RetentionPolicy.RUNTIME):确保注解在运行时可通过反射获取
  • mustRole属性:定义方法访问所需的最小权限
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface AuthCheck {
/**
* 访问该方法必须拥有的角色
* @default "" 表示不需要特定角色
*/
String mustRole() default "";
}

2. AOP拦截器实现机制

Spring AOP通过动态代理实现方法拦截。我们的AuthInterceptor核心功能包括:

  1. 通过@Around注解定义切入点
  2. 获取方法上的@AuthCheck注解配置
  3. 从请求上下文获取当前用户
  4. 执行角色权限校验
  5. 决定是否放行或拦截
@Aspect
@Component
public class AuthInterceptor {

@Resource
private UserService userService;

@Around("@annotation(authCheck)")
public Object doInterceptor(ProceedingJoinPoint joinPoint, AuthCheck authCheck) throws Throwable {
// 获取注解配置的必须角色
String mustRole = authCheck.mustRole();

// 获取当前请求对象
RequestAttributes requestAttributes = RequestContextHolder.currentRequestAttributes();
HttpServletRequest request = ((ServletRequestAttributes) requestAttributes).getRequest();

// 获取当前登录用户
User loginUser = userService.getLoginUser(request);

// 角色枚举转换
UserRoleEnum mustRoleEnum = UserRoleEnum.getEnumByValue(mustRole);

// 无需特定角色则直接放行
if (mustRoleEnum == null) {
return joinPoint.proceed();
}

// 获取用户角色
UserRoleEnum userRoleEnum = UserRoleEnum.getEnumByValue(loginUser.getUserRole());

// 角色校验失败处理
if (userRoleEnum == null) {
throw new BusinessException(ErrorCode.NO_AUTH_ERROR);
}

// 管理员权限特殊校验
if (UserRoleEnum.ADMIN.equals(mustRoleEnum) && !UserRoleEnum.ADMIN.equals(userRoleEnum)) {
throw new BusinessException(ErrorCode.NO_AUTH_ERROR);
}

// 校验通过,执行原方法
return joinPoint.proceed();
}
}

🚀 实际应用示例

管理员专属接口

@RestController
@RequestMapping("/admin")
public class AdminController {

@AuthCheck(mustRole = "admin")
@GetMapping("/users")
public List<User> getAllUsers() {
return userService.list();
}

@AuthCheck(mustRole = "admin")
@PostMapping("/reset-password")
public boolean resetPassword(@RequestParam Long userId) {
return userService.resetPassword(userId);
}
}

混合权限接口

@RestController
@RequestMapping("/api")
public class UserController {

// 无需特殊权限
@GetMapping("/profile")
public User getProfile() {
return userService.getCurrentUser();
}

// 需要管理员权限
@AuthCheck(mustRole = "admin")
@DeleteMapping("/user/{id}")
public boolean deleteUser(@PathVariable Long id) {
return userService.deleteById(id);
}
}

📊 方案优势分析

对比维度传统方式AOP+注解方式
代码侵入性
可维护性差(分散在各处)好(集中管理)
开发效率低(重复编码)高(声明式配置)
性能影响每次查询数据库可缓存权限信息
可读性业务逻辑混杂关注点分离
最后修改:2025 年 09 月 26 日
如果觉得我的文章对你有用,请随意赞赏