Spring Security自定义AccessDeniedHandler未触发问题排查
问题根因
你的判断完全正确:标注@Provider的ExceptionMH是JAX-RS规范(Jersey/Resteasy等框架)的全局异常处理器,执行优先级高于Spring Security配置的AccessDeniedHandler。
你开启了global-method-security的pre-post-annotations,@PreAuthorize等注解做方法级权限校验时抛出的AccessDeniedException,会先被JAX-RS的异常捕获机制拦截,直接生成响应返回,根本不会传递到Spring Security Filter链的异常处理节点,自然触发不了自定义的AccessDeniedHandler。
另外你写的CustomAccessDeniedHandler本身也有缺陷:就算触发,只打印日志不写入响应、不做跳转/转发,会导致请求直接挂住。
修复方案
推荐选第一种方案,改动最小、最适配你当前的混合技术栈:
方案1:将访问日志逻辑迁移到JAX-RS异常处理器中
既然AccessDeniedException已经被ExceptionMH捕获,直接在对应分支加日志逻辑即可,不需要强行调整过滤器优先级:
- 给
ExceptionMH注入请求上下文,获取当前请求URI:
@Context private HttpServletRequest request;
- 修改
AccessDeniedException的处理分支,补充日志逻辑,同时修正状态码错误:
} else if (exception.getClass() == AccessDeniedException.class) { // 补充你需要的URI记录逻辑 Authentication auth = SecurityContextHolder.getContext().getAuthentication(); if (auth != null) { logger.warn("User: " + auth.getName() + " attempted to access the protected URL: " + request.getRequestURI()); } this.exceptionError(exception); // 注意:无权限访问对应HTTP状态码是403,你之前写的401是未认证语义,不符合规范 return Response.status(403).entity("{ \"error\": \"" + exception.getMessage() + "\" }").build(); }
- 删掉Spring Security配置中冗余的
<access-denied-handler ref="customAccessDeniedHandler"/>和对应Bean定义即可。
额外冗余代码提示:ExceptionMH不需要继承Throwable,ExceptionMapper接口不要求实现类是异常类型,这个继承会增加不必要的异常栈开销,可以直接删掉。
方案2:强制调整异常处理优先级(不推荐)
如果一定要走AccessDeniedHandler逻辑,需要做两层改动:
- 调整Spring Security过滤器链的加载顺序,把
ExceptionTranslationFilter放到JAX-RS过滤器之前执行 - 额外配置方法级安全的异常透传规则,让
@PreAuthorize等注解抛出的AccessDeniedException不被JAX-RS提前拦截,透传到外层Filter链处理
这个方案改动链路长,很容易导致其他异常的拦截逻辑失效,除非有特殊需求否则不建议使用。
如果你要保留AccessDeniedHandler的写法,必须补全响应逻辑
如果后续场景需要用到AccessDeniedHandler,一定要在日志打印后补全响应输出,避免请求挂起:
@Override public void handle( HttpServletRequest request, HttpServletResponse response, AccessDeniedException exc) throws IOException, ServletException { Authentication auth = SecurityContextHolder.getContext().getAuthentication(); if (auth != null) { logger.warn("User: " + auth.getName() + " attempted to access the protected URL: " + request.getRequestURI()); } // 补全响应逻辑 response.setStatus(HttpServletResponse.SC_FORBIDDEN); response.setContentType("application/json;charset=UTF-8"); response.getWriter().write("{ \"error\": \"" + exc.getMessage() + "\" }"); }
内容的提问来源于stack exchange,提问作者Paul Serre
相关产品推荐
相关产品推荐

