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

Spring MVC集成POST API时出现400 Bad Request问题求助

问题分析与解决方案

400 Bad Request 通常源于请求格式不匹配或请求数据被过滤器篡改,结合你提到的 Apache WsFilter,重点排查以下几个方向:

1. WsFilter 提前读取请求体导致解析失败

Apache WsFilter 可能会提前读取 HTTP 请求体,而 Spring MVC 的 @RequestBody 依赖请求体的可重复读取能力。如果请求体被过滤器读取后未重置流,Spring MVC 就无法再解析表单数据,直接返回 400 错误。

解决办法:
自定义请求包装类缓存请求体,让过滤器和 Spring MVC 都能读取:

public class CachedBodyHttpServletRequestWrapper extends HttpServletRequestWrapper {
    private final byte[] cachedBody;

    public CachedBodyHttpServletRequestWrapper(HttpServletRequest request) throws IOException {
        super(request);
        this.cachedBody = StreamUtils.copyToByteArray(request.getInputStream());
    }

    @Override
    public ServletInputStream getInputStream() throws IOException {
        return new CachedBodyServletInputStream(cachedBody);
    }

    @Override
    public BufferedReader getReader() throws IOException {
        return new BufferedReader(new InputStreamReader(new ByteArrayInputStream(cachedBody)));
    }

    private static class CachedBodyServletInputStream extends ServletInputStream {
        private final ByteArrayInputStream byteArrayInputStream;

        public CachedBodyServletInputStream(byte[] cachedBody) {
            this.byteArrayInputStream = new ByteArrayInputStream(cachedBody);
        }

        @Override
        public int read() throws IOException {
            return byteArrayInputStream.read();
        }

        @Override
        public boolean isFinished() {
            return byteArrayInputStream.available() == 0;
        }

        @Override
        public boolean isReady() {
            return true;
        }

        @Override
        public void setReadListener(ReadListener readListener) {
            throw new UnsupportedOperationException("Not supported");
        }
    }
}

修改 WsFilter 的 doFilter 方法,传递包装后的请求:

@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
    HttpServletRequest httpRequest = (HttpServletRequest) request;
    CachedBodyHttpServletRequestWrapper cachedRequest = new CachedBodyHttpServletRequestWrapper(httpRequest);
    chain.doFilter(cachedRequest, response);
}

2. 替换 @RequestBody 为 @RequestParam 或 @ModelAttribute

既然独立 Spring Boot 项目中 @RequestParam 能正常工作,在集成项目中改用这种方式可以避免请求体解析的冲突:

单参数场景:

@RequestMapping(value = "/registration", method = RequestMethod.POST, consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE)
public void marketplaceRegistration(@RequestParam("token") String token, HttpServletRequest httpRequest, HttpServletResponse httpResponse) throws IOException {
    LOGGER.info("\nIn registration method: TOKEN RECIEVED IS " + token);
}

多参数场景:
先定义表单实体类:

public class RegistrationForm {
    private String token;
    // 其他参数的 getter/setter
}

再用 @ModelAttribute 接收:

@RequestMapping(value = "/registration", method = RequestMethod.POST, consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE)
public void marketplaceRegistration(@ModelAttribute RegistrationForm form, HttpServletRequest httpRequest, HttpServletResponse httpResponse) throws IOException {
    LOGGER.info("\nIn registration method: TOKEN RECIEVED IS " + form.getToken());
}

3. 调整 WsFilter 的执行顺序

确保 WsFilter 的执行顺序在 Spring MVC 的 DispatcherServlet 之后,避免它干扰请求解析流程。

在 web.xml 中调整配置顺序:

<!-- 先配置 Spring MVC 的 DispatcherServlet -->
<servlet>
    <servlet-name>dispatcher</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
    <servlet-name>dispatcher</servlet-name>
    <url-pattern>/</url-pattern>
</servlet-mapping>

<!-- 再配置 WsFilter -->
<filter>
    <filter-name>wsFilter</filter-name>
    <filter-class>org.apache.ws.commons.server.WsFilter</filter-class>
</filter>
<filter-mapping>
    <filter-name>wsFilter</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>

4. 检查请求头是否被篡改

WsFilter 可能会修改请求的 Content-Type 头,导致 Spring MVC 无法识别 x-www-form-urlencoded 格式。

调试方法:在 WsFilter 中添加日志打印请求头:

@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
    HttpServletRequest httpRequest = (HttpServletRequest) request;
    LOGGER.info("Request Content-Type: " + httpRequest.getContentType());
    chain.doFilter(request, response);
}

如果发现 Content-Type 被篡改,可在过滤器中手动重置:

((HttpServletRequest)request).setAttribute("Content-Type", MediaType.APPLICATION_FORM_URLENCODED_VALUE);

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.09 08:01:22