Spring Boot 3.2+Spring Security 6.2中每次成功API调用前触发/error端点的异常问题排查与修复
嘿,这个问题我之前也踩过坑,绝对不是正常现象——每次正常请求前都跑一遍/error端点完全是冗余的,不仅占日志篇幅,还可能带来不必要的性能开销。咱们先搞清楚为啥会这样,再一步步把它干掉。
问题根因分析
你看到的/error端点调用,本质是Spring Security默认的BasicAuthenticationEntryPoint在搞事情。当这个EntryPoint处理认证相关逻辑时,它会调用response.sendError(401)方法返回未授权响应——而这个方法会触发Servlet容器的错误处理机制,直接把请求dispatch到Spring MVC默认的/error端点生成错误响应。
但你明明已经在请求里带了正确的Basic Auth信息,为什么还会触发这个逻辑?其实是认证流程里的一个设计小坑:在Spring Security的Filter Chain处理请求的早期阶段,某个临时的未认证标记触发了EntryPoint的sendError,但后续的认证逻辑又成功通过了,导致最终请求正常处理,但/error的dispatch已经发出去了。
修复方案:自定义AuthenticationEntryPoint,跳过Error Controller
最直接的修复方式是替换掉默认的BasicAuthenticationEntryPoint,自己实现一个不调用sendError的EntryPoint,直接手动写入401响应头,这样就不会触发/error端点的dispatch了。
步骤1:自定义Basic认证EntryPoint
写一个类继承BasicAuthenticationEntryPoint,重写commence方法:
import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import org.springframework.security.core.AuthenticationException; import org.springframework.security.web.authentication.www.BasicAuthenticationEntryPoint; import java.io.IOException; public class CustomBasicAuthEntryPoint extends BasicAuthenticationEntryPoint { @Override public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException { // 直接设置响应状态和头,不调用sendError,避免触发/error dispatch response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); response.setHeader("WWW-Authenticate", getRealmName()); // 可选:如果需要返回JSON格式错误体,添加以下代码 response.setContentType("application/json"); response.getWriter().write("{\"error\":\"Unauthorized\",\"status\":401}"); } @Override public void afterPropertiesSet() { setRealmName("YourServiceRealm"); // 替换成你的服务Realm名称 super.afterPropertiesSet(); } }
步骤2:在Security配置中注册并使用自定义EntryPoint
修改你的WebSecurityConfig,把默认的Http Basic EntryPoint替换成咱们自定义的:
@Configuration @EnableWebSecurity public class WebSecurityConfig { @Autowired UserDetailsServiceImpl userDetailsService; // 注册自定义EntryPoint的Bean @Bean public CustomBasicAuthEntryPoint customBasicAuthEntryPoint() { return new CustomBasicAuthEntryPoint(); } @Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { http .sessionManagement(session -> session .sessionCreationPolicy(SessionCreationPolicy.STATELESS)) // 替换默认的Http Basic配置,使用自定义EntryPoint .httpBasic(basic -> basic.authenticationEntryPoint(customBasicAuthEntryPoint())) .authorizeHttpRequests(auth -> auth .requestMatchers("/error", "/health").permitAll() .anyRequest().authenticated()) .requestCache(cache -> cache.disable()) .csrf(csrf -> csrf.disable()); return http.build(); } }
步骤3:验证修复效果
重启服务后再测试API调用,你会发现/error端点的DEBUG日志彻底消失了,整个请求流程会变成:
- UserDetailsService加载用户信息
- CommonsRequestLoggingFilter记录请求开始
- 正常处理业务端点并返回结果
- CommonsRequestLoggingFilter记录请求结束
完全没有多余的/error dispatch了!
为什么这个方案有效?
核心区别在于:
- 默认EntryPoint用
response.sendError(401):会触发Servlet容器的错误处理流程,自动dispatch到/error端点 - 自定义EntryPoint用
response.setStatus(401)直接设置响应:只是修改响应状态码和头,不会触发任何额外的错误dispatch流程
这样既保留了Basic Auth需要的WWW-Authenticate头,又彻底避免了不必要的/error端点调用。
补充:如果不需要错误响应体怎么办?
如果你不需要返回JSON格式的错误体,只需要最基础的401响应,自定义EntryPoint可以更简洁:
@Override public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException { response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); response.setHeader("WWW-Authenticate", getRealmName()); }
这样连响应体都不会写,完全跳过Spring MVC的Error处理逻辑,性能更优。




