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

Spring Security中捕获RequestRejectedException后仍记录未捕获错误日志的问题求助

问题原因分析

这个情况的核心原因是过滤器执行顺序的问题:你的RequestRejectedExceptionHandler过滤器虽然设置了@Order(Ordered.HIGHEST_PRECEDENCE),但实际上它的执行顺序在Spring Cloud Sleuth的ExceptionLoggingFilter之后。

从你提供的异常栈可以看到,ExceptionLoggingFilter是在Spring Security过滤器链的上游执行的。当StrictHttpFirewall抛出RequestRejectedException时,异常会沿着过滤器链反向传播:先被ExceptionLoggingFilter捕获,它会记录一条ERROR级别的“未捕获异常”日志,然后将异常重新抛出;之后异常才会传到你的自定义过滤器,此时你虽然处理了异常并返回400响应,但ERROR日志已经被记录下来了。

另外,ExceptionLoggingFilter的源码逻辑就是捕获所有通过它的异常,打日志后重新抛出,所以只要它在你的过滤器之前,就一定会先记录这条日志。


解决方案

有两种靠谱的解决方式,推荐第一种,因为它更贴合Spring Security的设计:

方案1:使用Spring Security内置的RequestRejectedHandler处理异常

Spring Security本身就提供了处理RequestRejectedException的扩展点,我们可以自定义一个RequestRejectedHandler,让异常在Spring Security内部就被处理,不会传播到Servlet Filter链中,自然也就不会触发ExceptionLoggingFilter的日志。

步骤如下:

  1. 自定义RequestRejectedHandler实现类:
@Component
public class CustomRequestRejectedHandler implements RequestRejectedHandler {
    private static final Logger log = LoggerFactory.getLogger(CustomRequestRejectedHandler.class);

    @Override
    public void handle(HttpServletRequest request, HttpServletResponse response, RequestRejectedException ex) throws IOException {
        // 这里记录WARN级别日志即可,不需要ERROR
        log.warn("请求被拒绝:非法URL格式,URL={}", request.getRequestURL(), ex);
        // 设置400响应
        response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
        response.setContentType("application/json");
        response.setCharacterEncoding("UTF-8");
        // 可以写入自定义的JSON响应体
        response.getWriter().write("{\"code\":400,\"message\":\"非法的请求URL格式\"}");
    }
}
  1. 在Spring Security配置类中配置这个处理器:
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    private final CustomRequestRejectedHandler customRequestRejectedHandler;

    public SecurityConfig(CustomRequestRejectedHandler customRequestRejectedHandler) {
        this.customRequestRejectedHandler = customRequestRejectedHandler;
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            // 这里添加你的其他Security配置
            .authorizeRequests()
                .anyRequest().authenticated()
                .and()
            .exceptionHandling()
                // 配置自定义的RequestRejectedHandler
                .requestRejectedHandler(customRequestRejectedHandler);
    }

    // 如果需要自定义StrictHttpFirewall的规则(比如是否允许双斜杠),可以添加这个Bean
    @Bean
    public StrictHttpFirewall strictHttpFirewall() {
        StrictHttpFirewall firewall = new StrictHttpFirewall();
        // 如果你需要允许URL中的双斜杠,可以打开下面的注释,但不建议,存在安全风险
        // firewall.setAllowUrlEncodedDoubleSlash(true);
        return firewall;
    }

    @Override
    public void configure(WebSecurity web) throws Exception {
        web.httpFirewall(strictHttpFirewall());
    }
}

这样配置后,当StrictHttpFirewall抛出异常时,Spring Security会直接调用我们自定义的处理器,异常不会传播到Servlet Filter链,ExceptionLoggingFilter也就不会记录那条ERROR日志了。

方案2:调整自定义过滤器的执行顺序,确保它在ExceptionLoggingFilter之前

如果你坚持使用Servlet Filter的方式处理,需要确保你的过滤器在ExceptionLoggingFilter之前执行。

Spring Cloud Sleuth的ExceptionLoggingFilter默认的Order是Ordered.HIGHEST_PRECEDENCE + 9,所以我们可以通过FilterRegistrationBean手动注册过滤器,设置一个更小的Order值(优先级更高):

  1. 移除原过滤器类上的@Component注解,然后创建一个配置类注册过滤器:
@Configuration
public class FilterRegistrationConfig {
    @Bean
    public FilterRegistrationBean<RequestRejectedExceptionHandler> requestRejectedHandlerFilter() {
        FilterRegistrationBean<RequestRejectedExceptionHandler> registrationBean = new FilterRegistrationBean<>();
        registrationBean.setFilter(new RequestRejectedExceptionHandler());
        // 设置比ExceptionLoggingFilter更高的优先级
        registrationBean.setOrder(Ordered.HIGHEST_PRECEDENCE);
        // 匹配所有请求路径
        registrationBean.addUrlPatterns("/*");
        return registrationBean;
    }
}
  1. 确保原过滤器的doFilterInternal方法中处理完响应后,不要重新抛出异常(你的代码已经做到了这一点):
@Slf4j
public class RequestRejectedExceptionHandler extends OncePerRequestFilter {
    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        try {
            filterChain.doFilter(request, response);
        } catch (RequestRejectedException e) {
            log.warn("400 - Bad Request: 非法URL格式", e);
            response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
            response.setContentType("application/json");
            response.setCharacterEncoding("UTF-8");
            response.getWriter().write("{\"code\":400,\"message\":\"非法的请求URL格式\"}");
            // 不要抛出异常,避免继续传播
        }
    }
}

这样,你的过滤器会先于ExceptionLoggingFilter执行,当异常抛出时会被你的过滤器捕获并处理,不会传播到ExceptionLoggingFilter,也就不会产生那条多余的ERROR日志了。


内容的提问来源于stack exchange,提问作者amasuKAKAROT

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.29 21:53:13