You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

Spring Boot 3.2+Spring Security 6.2中每次成功API调用前触发/error端点的异常问题排查与修复

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处理逻辑,性能更优。

火山引擎 最新活动