如何处理/抑制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
相关产品推荐
相关产品推荐

