Spring Security中ControllerAdvice致AccessDeniedHandler未调用
根因说明
这个问题来自Spring Security异常处理的作用域边界:
- 配置在
HttpSecurity中的AccessDeniedHandler仅由过滤器链中的ExceptionTranslationFilter触发,只处理过滤器链执行阶段产生的权限异常,比如URL规则匹配后判定权限不足的场景。 @EnableGlobalMethodSecurity开启的方法级校验(@PreAuthorize/@PostAuthorize等)基于Spring AOP实现,拦截点在Controller方法调用阶段,这一层抛出的AccessDeniedException会直接进入Spring MVC的异常分发流程,被@ControllerAdvice的@ExceptionHandler优先捕获,根本不会回传到过滤器链,自然触发不到你配置的AccessDeniedHandler。AccessDeniedException继承自RuntimeException,因此会被你定义的通用RuntimeException全局处理器兜底,错误返回500状态码。
解决方法
不需要修改现有RuntimeException全局处理逻辑,选下面最简便的方案即可:
推荐方案:在ControllerAdvice中新增AccessDeniedException专属处理方法
Spring MVC的异常匹配规则会优先命中类型更具体的@ExceptionHandler,你只需要在现有全局异常类中注入自定义的AccessDeniedHandler,新增一个专门处理AccessDeniedException的方法,直接复用已有Handler的逻辑即可,不会影响原有RuntimeException的处理。
代码示例:
// 注入你已经实现好的自定义AccessDeniedHandler @Autowired private ThingspodAccessDeniedHandler accessDeniedHandler; @ExceptionHandler(AccessDeniedException.class) public void handleMethodLevelAccessDenied(AccessDeniedException ex, HttpServletRequest request, HttpServletResponse response) throws IOException { // 直接调用已有Handler逻辑,保证所有权限拒绝场景的响应格式完全统一 accessDeniedHandler.handle(request, response, ex); } // 原有RuntimeException全局处理逻辑完全保留,不需要做任何修改 @ExceptionHandler(RuntimeException.class) public ResponseEntity<Object> handleAllUncaughtRuntimeException( RuntimeException ex, WebRequest request){ return buildApiErrorResponse(ex, HttpStatus.INTERNAL_SERVER_ERROR, ThingspodErrorCode.GENERAL, request); }
可选配置优化
你当前的Security配置中重复调用了两次exceptionHandling(),虽然不会造成功能异常,但可以合并简化,避免冗余配置:
@Override protected void configure(HttpSecurity http) throws Exception { http.headers().cacheControl().and().frameOptions().disable() .and() .cors() .and() .csrf().disable() .exceptionHandling() // 把accessDeniedHandler配置统一放在exceptionHandling块下 .accessDeniedHandler(accessDeniedHandler) .and() .sessionManagement() .sessionCreationPolicy(SessionCreationPolicy.STATELESS) .and() .authorizeRequests() .antMatchers(paymentCallbackPath).permitAll() .antMatchers(TOKEN_BASED_AUTH_ENTRY_POINT).authenticated() .and() .addFilterBefore(jwtAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class); }
不推荐通过重写GlobalMethodSecurityConfiguration的方式透传异常,配置复杂度高,后续版本升级兼容性差,没有必要。
内容的提问来源于stack exchange,提问作者sobhan nami
相关产品推荐
相关产品推荐

