权限校验必须用@Around而非@Before,因其可控制执行、捕获异常、修改返回;@Before无法阻止方法执行。需支持AND/OR多权限组合,从统一SecurityContext提取用户身份,注解应覆盖Controller与Service层,并注意异步场景上下文传递。

权限校验逻辑必须放在 @Around 而不是 @Before
因为只有 @Around 能控制方法是否执行、捕获异常、修改返回值。用 @Before 只能做前置检查,一旦权限不通过,业务方法仍会执行——这等于没拦住。
常见错误现象:加了注解但权限失败后接口仍返回数据,或抛出 500 而非 403。
-
@Around方法签名必须接收ProceedingJoinPoint和注解参数(如RequiresPermissions permissions) - 调用
joinPoint.proceed()前做权限判断,不通过直接 throw 异常(如new AccessDeniedException("无权限")) - 不要在
@Around里吞掉异常,否则全局异常处理器收不到
@RequiresPermissions 注解要支持 AND/OR 多权限组合
真实业务中权限不是单点开关,比如「导出+审核」需同时满足,而「查看订单」或「查看报表」满足其一即可。硬编码成单字符串会很快卡死迭代。
示例注解定义:
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface RequiresPermissions {
String[] value() default {};
Logical logical() default Logical.AND; // enum { AND, OR }
}
-
value()支持数组,如@RequiresPermissions({"order:export", "order:audit"}) -
logical = Logical.OR时,只要用户有任一权限就放行 - AOP 切面里用
authService.hasPermissions(userId, permissions, logical)封装校验逻辑,别在切面里写 if-else 判断
从请求上下文提取用户身份不能只靠 RequestContextHolder
Spring Boot WebMvc 环境下 RequestContextHolder 可用,但 WebFlux、异步线程、定时任务中它会为空——这时切面一运行就 NPE。
更健壮的做法是统一抽象一个 SecurityContext 工具类:
public class SecurityContext {
public static String getCurrentUserId() {
// 优先从 ReactiveSecurityContextHolder(WebFlux)
// 其次从 SecurityContextHolder(MVC)
// 最后 fallback 到 ThreadLocal 存储的 token 解析结果
return ...;
}
}
- 避免在切面里直接写
((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest() - 如果项目已集成 Spring Security,优先用
SecurityContextHolder.getContext().getAuthentication() - Token 解析逻辑(如 JWT)应抽离为独立 service,切面只负责调用,不耦合解析细节
权限注解生效范围要覆盖 Controller 层 + Service 层
只打在 Controller 上看似够用,但实际会漏掉两类关键路径:一是 Feign 远程调用后的本地 Service 方法,二是 MQ 消费端处理逻辑——它们没有 HTTP 请求上下文,但仍有权限语义。
- Controller 层注解用于入口级粗粒度拦截(如角色维度)
- Service 层注解用于细粒度操作级控制(如
@RequiresPermissions("user:delete:own")) - 注意:Service 方法被本类其他方法直接调用时,AOP 不生效(代理失效),必须走 Spring 容器注入的 bean 引用
最易被忽略的是异步场景下的上下文传递——比如用 @Async 执行带权限注解的方法,SecurityContext 默认不会继承。不显式传播,切面拿到的就是 null 用户 ID。

















