如何让Spring AOP切面在控制器参数验证前执行?
问题:如何让权限校验切面在请求体验证前执行?
现有代码中,控制器方法通过@Valid注解做请求体验证,同时用@AllowedScopes注解配合切面做权限校验,但目前切面逻辑会在请求体验证通过后才执行,导致请求体验证失败时先返回验证错误,权限不足时才返回403。需要调整逻辑,让权限校验在请求体绑定和验证之前执行。
原代码如下:
class OrderController { @AllowedScopes({ORDER_CREATE}) @PostMapping("/create") public CreateOrderResponse createOrder(@Valid @RequestBody OrderRequest request){ } } @Aspect @Component public class AllowedScopeAspect { @Pointcut("@annotation(allowedScopes)") private void callAtAllowedScopes(AllowedScopes allowedScopes) { // just a pointcut signature } @Before(value = "callAtAllowedScopes(allowedScopes)", argNames = "jp,allowedScopes") public void validateScope(JoinPoint jp, AllowedScopes allowedScopes) { ... } }
解决方案
方法1:改用Spring MVC拦截器(推荐)
Spring MVC的拦截器preHandle方法会在请求进入控制器方法、完成参数绑定和验证之前触发,刚好符合权限校验前置的需求。
- 自定义拦截器实现权限校验:
@Component public class ScopeValidationInterceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { // 仅处理控制器方法的请求 if (handler instanceof HandlerMethod handlerMethod) { AllowedScopes allowedScopes = handlerMethod.getMethodAnnotation(AllowedScopes.class); if (allowedScopes != null) { // 执行你的权限校验逻辑 String[] requiredScopes = allowedScopes.value(); boolean hasPermission = checkUserScopes(requiredScopes); // 替换为实际校验逻辑 if (!hasPermission) { response.setStatus(HttpStatus.FORBIDDEN.value()); return false; } } } return true; } // 示例权限校验方法 private boolean checkUserScopes(String[] requiredScopes) { // 这里实现获取用户当前权限并和所需权限对比的逻辑 return false; } }
- 注册拦截器到Spring MVC配置:
@Configuration public class WebMvcConfig implements WebMvcConfigurer { private final ScopeValidationInterceptor scopeValidationInterceptor; // 构造注入拦截器 public WebMvcConfig(ScopeValidationInterceptor scopeValidationInterceptor) { this.scopeValidationInterceptor = scopeValidationInterceptor; } @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(scopeValidationInterceptor) // 配置需要拦截的路径,比如所有接口请求 .addPathPatterns("/**"); } }
方法2:调整切面切入点,拦截更早的执行阶段
如果坚持使用AspectJ切面,可以把切入点定位到Spring MVC处理请求的核心方法,这样能在参数解析和验证前触发权限校验。
修改后的切面代码:
@Aspect @Component public class AllowedScopeAspect { // 切入点定位到RequestMappingHandlerAdapter的invokeHandlerMethod方法 @Pointcut("execution(* org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(..))") private void invokeHandlerMethod() {} @Before("invokeHandlerMethod()") public void validateScope(JoinPoint joinPoint) { // 从方法参数中获取HandlerMethod(控制器方法的封装对象) Object[] args = joinPoint.getArgs(); HandlerMethod handlerMethod = (HandlerMethod) args[0]; // 检查当前控制器方法是否标注了@AllowedScopes AllowedScopes allowedScopes = handlerMethod.getMethodAnnotation(AllowedScopes.class); if (allowedScopes != null) { // 执行权限校验逻辑 boolean hasPermission = checkUserScopes(allowedScopes.value()); // 替换为实际校验逻辑 if (!hasPermission) { throw new AccessDeniedException("无权限执行该操作"); } } } // 示例权限校验方法 private boolean checkUserScopes(String[] requiredScopes) { // 实现具体的权限对比逻辑 return false; } }
原切面延迟执行的原因
原切面的切入点@annotation(allowedScopes)默认拦截的是控制器方法的执行阶段,而Spring MVC的@Valid参数验证是在控制器方法执行前的参数解析环节完成的,所以切面逻辑会在验证之后触发。
内容的提问来源于stack exchange,提问作者Sarvar Nishonboyev
相关产品推荐
相关产品推荐

