Spring Boot过滤器抛出InternalServerErrorException未返回客户端,客户端收到403错误的原因咨询
你遇到的核心问题是:过滤器中抛出的异常无法被@ControllerAdvice捕获。
原因很简单:@ControllerAdvice注解的异常处理器是Spring MVC层面的组件,它只能处理DispatcherServlet调度之后(也就是控制器层、服务层等Spring管理的Bean中)抛出的异常。而你的过滤器属于Servlet容器的过滤链,执行顺序在DispatcherServlet之前,所以过滤器里抛出的异常根本不会进入Spring MVC的异常处理流程,自然无法被你的ApiResponseEntityExceptionHandler捕获。
这也是为什么你在过滤器外抛出InternalServerErrorException能正常返回自定义响应,但过滤器里抛就不行——客户端收到的403其实是Spring Security默认的异常处理结果(因为过滤器属于安全过滤链的一部分,异常被Spring Security的默认处理器拦截返回了403)。
下面提供两种最直接的解决方式,你可以根据自己的场景选择:
方案1:在过滤器中手动处理异常,直接写入响应体
既然@ControllerAdvice管不到过滤器的异常,那我们可以在过滤器的catch块里直接构建响应,写入到HttpServletResponse中:
import com.fasterxml.jackson.databind.ObjectMapper; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.web.filter.OncePerRequestFilter; import javax.servlet.FilterChain; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.text.SimpleDateFormat; import java.util.Date; @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException { try { // 原有逻辑保持不变 String header = request.getHeader(JwtConstant.AUTHORIZATION_HEADER_STRING); if (header == null || !header.startsWith(JwtConstant.TOKEN_BEARER_PREFIX)) { chain.doFilter(request, response); return; } UsernamePasswordAuthenticationToken authorization = authorizeRequest(request); SecurityContextHolder.getContext().setAuthentication(authorization); chain.doFilter(request, response); } catch (Exception e) { SecurityContextHolder.clearContext(); // 手动构建自定义异常响应 response.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value()); response.setContentType(MediaType.APPLICATION_JSON_VALUE); ExceptionResponse exceptionResponse = new ExceptionResponse( new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()), "Erreur sur le filtre interne -> " + e.toString(), request.getRequestURI(), // 用getRequestURI替代request.getDescription(false)更简洁 "500" ); // 用ObjectMapper将对象序列化为JSON写入响应 ObjectMapper objectMapper = new ObjectMapper(); objectMapper.writeValue(response.getOutputStream(), exceptionResponse); } }
方案2:利用Spring Security的异常处理机制(如果你用了Spring Security)
如果你的过滤器是Spring Security过滤链的一部分,可以自定义AuthenticationEntryPoint来统一处理过滤链中的异常:
第一步:自定义AuthenticationEntryPoint
import com.fasterxml.jackson.databind.ObjectMapper; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.security.core.AuthenticationException; import org.springframework.security.web.AuthenticationEntryPoint; import org.springframework.stereotype.Component; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.text.SimpleDateFormat; import java.util.Date; @Component public class CustomAuthEntryPoint implements AuthenticationEntryPoint { private final ObjectMapper objectMapper; private final String typeDateFormat = "yyyy-MM-dd HH:mm:ss"; // 注入Spring容器中的ObjectBean,避免重复创建 public CustomAuthEntryPoint(ObjectMapper objectMapper) { this.objectMapper = objectMapper; } @Override public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException { // 处理过滤链中的异常,包括你过滤器抛出的异常 response.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value()); response.setContentType(MediaType.APPLICATION_JSON_VALUE); ExceptionResponse exceptionResponse = new ExceptionResponse( new SimpleDateFormat(typeDateFormat).format(new Date()), "Erreur sur le filtre interne -> " + authException.toString(), request.getRequestURI(), "500" ); objectMapper.writeValue(response.getOutputStream(), exceptionResponse); } }
第二步:在Security配置中注册自定义EntryPoint
import org.springframework.context.annotation.Configuration; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; @Configuration @EnableWebSecurity public class SecurityConfig extends WebSecurityConfigurerAdapter { private final CustomAuthEntryPoint customAuthEntryPoint; public SecurityConfig(CustomAuthEntryPoint customAuthEntryPoint) { this.customAuthEntryPoint = customAuthEntryPoint; } @Override protected void configure(HttpSecurity http) throws Exception { http // 其他安全配置... .exceptionHandling() .authenticationEntryPoint(customAuthEntryPoint); // 替换默认的异常处理器 } }
为什么过滤器外的异常能正常工作?因为那些异常是在Spring管理的Bean(比如控制器、服务类)中抛出的,会经过DispatcherServlet的调度,进入Spring MVC的异常处理流程,这时@ControllerAdvice才能捕获并处理。而过滤器属于Servlet容器的范畴,不在Spring MVC的处理链路内,所以需要单独处理。
内容的提问来源于stack exchange,提问作者Kévin

