You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.28 10:27:46