如何捕获Spring Boot中@PreAuthorize抛出的AccessDeniedException
问题:无法捕获AccessDeniedException自定义权限拒绝响应
问题背景
我想捕获AccessDeniedException,在响应里显示自定义的权限拒绝提示“Access denied.”,而不是默认的“Internal Server Error(s) while executing query”。但调试时,try/catch块抓不到filterChain.doFilter(request, response)抛出的这个异常,用推荐的@ExceptionHandler也没效果。
当前响应
{ "errors": [ { "message": "Internal Server Error(s) while executing query" } ] }
期望响应
{ "errors": [ { "message": "Access denied." } ] }
现有代码
JwtFilter 代码
@Component public class JwtFilter extends OncePerRequestFilter { @Override protected void doFilterInternal(@NotNull HttpServletRequest request, @NotNull HttpServletResponse response, @NotNull FilterChain filterChain) { Optional<HttpServletRequest> optReq = Optional.of(request); String authToken = optReq .map(req -> req.getHeader("Authorization")) .filter(token -> !token.isEmpty()) .map(token -> token.replace("Bearer ", "")) .orElse(null); if (authToken != null && SecurityContextHolder.getContext().getAuthentication() == null) { //process token } else { try { filterChain.doFilter(request, response); } catch (AccessDeniedException | ServletException | IOException e) { String test = "test"; } } } }
使用@PreAuthorize的Mutation代码
@Component @PreAuthorize("hasAnyRole('CREATOR','INFLUENCER','INFLUENCER_TEAMMATE')") public class ProducerSharedMutations implements GraphQLMutationResolver { private final WidgetService widgetService; public ProducerSharedMutations(WidgetService widgetService) { this.widgetService = widgetService; } public Widget addWidget(WidgetInput widgetInput){ return widgetService.add(widgetInput); } }
解决方案
1. 捕获失败的原因
@PreAuthorize触发的AccessDeniedException是在Spring Security的方法拦截器阶段抛出的,你的JwtFilter处于过滤器链前端,此时方法层面的权限校验还未执行,所以filterChain.doFilter不会直接抛出该异常。另外,GraphQL会自动包裹原始异常,导致@ExceptionHandler无法匹配到目标异常类型。
2. 两种有效处理方式
方式一:自定义Spring Security的AccessDeniedHandler
创建自定义处理器替换默认逻辑,直接返回符合要求的响应:
@Component public class CustomAccessDeniedHandler implements AccessDeniedHandler { @Override public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException { response.setStatus(HttpServletResponse.SC_FORBIDDEN); response.setContentType("application/json"); String jsonResponse = "{\"errors\": [{\"message\": \"Access denied.\"}]}"; response.getWriter().write(jsonResponse); } }
在Spring Security配置类中注册该处理器:
@Configuration @EnableWebSecurity public class SecurityConfig { private final CustomAccessDeniedHandler customAccessDeniedHandler; public SecurityConfig(CustomAccessDeniedHandler customAccessDeniedHandler) { this.customAccessDeniedHandler = customAccessDeniedHandler; } @Bean public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { http .authorizeHttpRequests(auth -> auth .anyRequest().authenticated() ) .exceptionHandling(ex -> ex .accessDeniedHandler(customAccessDeniedHandler) ); return http.build(); } }
方式二:GraphQL专属全局异常处理
针对GraphQL接口,实现自定义GraphQLErrorHandler拦截异常并替换消息:
@Component public class CustomGraphQLErrorHandler implements GraphQLErrorHandler { @Override public List<GraphQLError> processErrors(List<GraphQLError> errors) { return errors.stream() .map(this::convertError) .collect(Collectors.toList()); } private GraphQLError convertError(GraphQLError error) { if (error instanceof ExceptionWhileDataFetching) { ExceptionWhileDataFetching exceptionError = (ExceptionWhileDataFetching) error; Throwable cause = exceptionError.getException(); if (cause instanceof AccessDeniedException) { return new GenericGraphQLError("Access denied."); } } return error; } }
3. 注意事项
- 确保
JwtFilter在Spring Security过滤器链中配置顺序正确,避免提前拦截导致后续权限校验逻辑无法执行。 - 若为GraphQL接口,优先使用方式二,更贴合GraphQL的响应格式规范。
内容的提问来源于stack exchange,提问作者Michael Bat
相关产品推荐
相关产品推荐

