在日常的后端开发中,接口的权限校验是一个绕不开的话题。相比于在每个 Controller 方法里写一堆 if-else 来判断当前登录用户的角色,使用 自定义注解 + AOP(面向切面编程) 无疑是一种更加优雅、解耦且易于维护的解决方案。
今天这篇文章,我将结合实际项目代码,手把手带你实现一个 @AuthCheck 注解。只要在对应的方法上贴上这个注解,就能轻松搞定“仅管理员可调用”的权限控制!
1. 引入 AOP 依赖
首先,确保你的 Spring Boot 项目中引入了 AOP 相关的 starter 依赖。在 pom.xml 中添加如下配置:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
2. 定义自定义注解 @AuthCheck
我们需要创建一个自定义注解,用于标记哪些方法需要进行权限校验。
package com.okcl.simpleserver.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 权限校验自定义注解
*/
@Target(ElementType.METHOD) // 作用于方法上
@Retention(RetentionPolicy.RUNTIME) // 在运行时生效
public @interface AuthCheck {
/**
* 需要校验的权限角色(例如:"admin")
*
* @return 权限字符串
*/
String hasRole() default "";
}
说明:@Target(ElementType.METHOD) 表示该注解只能用在方法上;hasRole 属性用于指定调用该方法所需的角色。
3. 在启动类上开启 AOP
为了让 AOP 切面生效,我们需要在 Spring Boot 的主启动类上加上 @EnableAspectJAutoProxy 注解:
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
@SpringBootApplication
@EnableAspectJAutoProxy(exposeProxy = true) // 开启 AOP 并暴露代理对象
public class SimpleServerApplication {
public static void main(String[] args) {
SpringApplication.run(SimpleServerApplication.class, args);
}
}
4. 编写核心逻辑:AOP 权限拦截器
这是整个权限校验的核心部分。我们会拦截所有标记了 @AuthCheck 注解的方法,在方法执行前进行角色判断。
注意:下方的
UserRoleEnum、ErrorCode、BusinessException以及获取用户信息的userService.getUserInfo()均为我项目中的自定义类和方法,大家在实际使用时请替换为自己项目中的用户上下文获取逻辑。
package com.okcl.simpleserver.aop;
import com.okcl.simpleserver.annotation.AuthCheck;
import com.okcl.simpleserver.enums.ErrorCode;
import com.okcl.simpleserver.enums.UserRoleEnum;
import com.okcl.simpleserver.exception.BusinessException;
import com.okcl.simpleserver.model.vo.UserVo;
import com.okcl.simpleserver.service.UserService;
import jakarta.annotation.Resource;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;
@Aspect // 声明这是一个切面类
@Component // 交给 Spring 容器管理
public class AuthInterceptor {
@Resource
private UserService userService;
/**
* 环绕通知,拦截带有 @AuthCheck 注解的方法
*/
@Around("@annotation(authCheck)")
public Object doInterceptor(ProceedingJoinPoint joinPoint, AuthCheck authCheck) throws Throwable {
// 1. 获取注解上要求的权限
String requiredRole = authCheck.hasRole();
UserRoleEnum enumByValue = UserRoleEnum.getEnumByValue(requiredRole);
// 如果注解没有设置角色要求,则直接放行
if (enumByValue == null) {
return joinPoint.proceed();
}
// 2. 获取当前登录用户信息
UserVo userInfo = userService.getUserInfo();
Integer currentUserRoleCode = userInfo.getUserRole(); // 当前登录角色的权限编码
UserRoleEnum userRoleEnum = UserRoleEnum.getEnumByCode(currentUserRoleCode);
// 3. 核心校验逻辑
if (userRoleEnum == null) {
// 用户未登录或角色异常
throw new BusinessException(ErrorCode.NOT_AUTH_ERROR);
}
// 如果要求是管理员,但当前用户不是管理员,则拦截并抛出无权限异常
if (UserRoleEnum.ADMIN.equals(enumByValue) && !UserRoleEnum.ADMIN.equals(userRoleEnum)) {
throw new BusinessException(ErrorCode.NOT_AUTH_ERROR);
}
// 4. 权限校验通过,继续执行原方法
return joinPoint.proceed();
}
}
5. 控制器中的实战使用
一切准备就绪,接下来看看如何在 Controller 中愉快地使用它。我们只需要在对应接口上添加 @AuthCheck(hasRole = "admin") 即可。
import com.okcl.simpleserver.annotation.AuthCheck;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/user")
public class UserController {
@Resource
private UserService userService;
/**
* 获取用户列表(仅管理员可用)
*
* @param userQueryDto 查询条件
* @return 分页用户数据
*/
@AuthCheck(hasRole = "admin") // 核心:加上这行注解,非管理员请求将直接被 AOP 拦截抛出异常
@PostMapping("/list")
public Result<Page<UserVo>> list(@RequestBody UserQueryDto userQueryDto) {
Page<UserVo> userVoPage = userService.queryUserList(userQueryDto);
return ResultUtils.success(userVoPage);
}
}
总结
通过 自定义注解 + AOP 的方式,我们将权限校验的逻辑从业务代码中彻底剥离了出来。这样做的好处非常明显:
代码侵入性低:Controller 层代码保持极致的干净整洁。
复用性强:后续如果有其他接口需要限制管理员访问,只需加一行
@AuthCheck(hasRole = "admin")即可。扩展性好:后续如果想扩展出“VIP用户可见”、“普通用户可见”等逻辑,只需要在
AuthInterceptor中补充对应的枚举校验即可。