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

如何处理/抑制Spring Boot DispatcherServlet的URLDecoder相关异常

处理Spring Boot 3.1中URLDecoder相关的无效请求错误

问题原因

你遇到的URLDecoder: Incomplete trailing escape (%) pattern错误,是在Servlet容器(如Tomcat)解析application/x-www-form-urlencoded类型请求体时触发的——这个阶段早于Spring Security过滤器链和DispatcherServlet的处理流程,因此你之前尝试的Security配置和@ControllerAdvice都无法捕获该异常,错误日志会直接由容器输出。

解决方案

方案1:自定义Tomcat Valve拦截解析异常(推荐)

通过自定义Tomcat Valve,在请求解析的早期阶段捕获URLDecoder异常,直接返回400 Bad Request响应,并抑制错误日志输出。

1.1 实现自定义Valve

import org.apache.catalina.connector.Request;
import org.apache.catalina.connector.Response;
import org.apache.catalina.valves.ValveBase;
import jakarta.servlet.ServletException;
import java.io.IOException;

public class UrlDecoderErrorValve extends ValveBase {

    @Override
    public void invoke(Request request, Response response) throws IOException, ServletException {
        try {
            // 主动触发参数解析,提前暴露异常
            request.getParameterMap();
            // 正常请求继续向下传递
            getNext().invoke(request, response);
        } catch (IllegalArgumentException e) {
            // 匹配URLDecoder相关异常
            if (e.getMessage() != null && e.getMessage().contains("URLDecoder: Incomplete trailing escape (%) pattern")) {
                response.sendError(400, "Invalid request parameters");
                return; // 终止请求流程,避免日志输出
            }
            // 非目标异常重新抛出
            throw e;
        }
    }
}

1.2 配置Tomcat加载自定义Valve

import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory;
import org.springframework.boot.web.server.WebServerFactoryCustomizer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class TomcatConfig {

    @Bean
    public WebServerFactoryCustomizer<TomcatServletWebServerFactory> tomcatValveCustomizer() {
        return factory -> factory.addEngineValves(new UrlDecoderErrorValve());
    }
}

方案2:调整日志配置抑制特定错误日志

如果不需要自定义响应,仅需隐藏错误日志,可以在application.properties中修改日志级别:

# 屏蔽dispatcherServlet的URLDecoder错误日志
logging.level.org.apache.catalina.core.ContainerBase.[Tomcat].[localhost].[/].[dispatcherServlet] = OFF

注意:此方法仅隐藏日志,容器仍会返回500状态码给请求方。

方案3:结合Spring ErrorController统一处理

通过配置让Tomcat将异常传递给Spring的ErrorController,再自定义处理逻辑:

3.1 配置application.properties

spring.mvc.throw-exception-if-no-handler-found=true
spring.web.resources.add-mappings=false

3.2 实现自定义ErrorController

import jakarta.servlet.RequestDispatcher;
import jakarta.servlet.http.HttpServletRequest;
import org.springframework.boot.web.servlet.error.ErrorController;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class CustomErrorController implements ErrorController {

    @RequestMapping("/error")
    public ResponseEntity<String> handleError(HttpServletRequest request) {
        Object exception = request.getAttribute(RequestDispatcher.ERROR_EXCEPTION);
        
        // 处理URLDecoder异常
        if (exception instanceof IllegalArgumentException e) {
            if (e.getMessage() != null && e.getMessage().contains("URLDecoder: Incomplete trailing escape (%) pattern")) {
                return ResponseEntity.badRequest().body("Invalid request parameters");
            }
        }

        // 处理其他错误
        Object statusCode = request.getAttribute(RequestDispatcher.ERROR_STATUS_CODE);
        HttpStatus status = statusCode != null ? 
            HttpStatus.valueOf(Integer.parseInt(statusCode.toString())) : 
            HttpStatus.INTERNAL_SERVER_ERROR;
        return ResponseEntity.status(status).body("An error occurred");
    }
}

安全补充配置

虽然这类无效请求本身无严重安全风险,但仍建议在SecurityFilterChain中限制允许的请求方法,避免不必要的请求尝试:

import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import static org.springframework.http.HttpMethod.GET;
import static org.springframework.http.HttpMethod.POST;

@Configuration
@EnableWebSecurity
public class SecurityConfig {

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http
            .authorizeHttpRequests(auth -> auth
                .requestMatchers(GET, "/**").permitAll()
                .requestMatchers(POST, "/**").permitAll()
                .anyRequest().denyAll()
            );
        return http.build();
    }
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.04 07:15:12