springmvc中 配置过滤器导致的post请求参数丢失问题
问题描述:
项目新增加功能,需要添加接口调用的入参验签,新增添加拦截器,并且配置了自定义BodyReaderHttpServletRequestWrapper实现流的复用,在不同的springboot版本中产生以下问题:
接口发送前提:POST multipart/form-data 请求
springboot 1.5.3.RELEASE 中发现过滤器中无法获取parameters 而controller层能获取到
springboot 2.2.5.RELEASE 中发现过滤器能获取parameters 而controller层不能获取到
//过滤器代码
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
ServletRequest requestWrapper = null;
HttpServletRequest httpRequest = (HttpServletRequest) request;
String contentType = httpRequest.getContentType();
if (request instanceof HttpServletRequest) {
// 将请求对象包装为 可重复读取流的请求对象。注意:构造好了,但是需要在拦截器中获取
/** 也可以使用 ContentCachingRequestWrapper **/
requestWrapper= new ContentCachingRequestWrapper((HttpServletRequest) request);
//自定义的wapper
// requestWrapper = new BodyReaderHttpServletRequestWrapper((HttpServletRequest) request);
}
chain.doFilter(requestWrapper, response);
}else{
chain.doFilter(request, response);
}
return;
}
public class BodyReaderHttpServletRequestWrapper extends HttpServletRequestWrapper {
private byte[] requestBody = null;// 用于将流保存下来
public BodyReaderHttpServletRequestWrapper(HttpServletRequest request) throws IOException {
super(request);
//注意此处已经调过inputStream
requestBody = StreamUtils.copyToByteArray(request.getInputStream());
}
@Override
public ServletInputStream getInputStream() throws IOException {
final ByteArrayInputStream bais = new ByteArrayInputStream(requestBody);
return new ServletInputStream() {
@Override
public int read() throws IOException {
return bais.read();
}
@Override
public boolean isFinished() {
return false;
}
@Override
public boolean isReady() {
return false;
}
@Override
public void setReadListener(ReadListener readListener) {
}
};
}
@Override
public BufferedReader getReader() throws IOException {
return new BufferedReader(new InputStreamReader(getInputStream()));
}
}
根本原因 request的getInputStream()/getReader() 与 getParameter() 的冲突问题
//代码定位
//查看request类的方法
@Override
public String getParameter(String name) {
if (!parametersParsed) {
//进入
parseParameters();
}
return coyoteRequest.getParameters().getParameter(name);
}
//问题在此处 如果已经调用过getInputStream/getReader 此次会直接返回
if (usingInputStream || usingReader) {
success = true;
return;
}
//此处如此设计是因为Servlet3.1有相关规范如下图
springboot版本不同 引起的结果不同:
在类WebMvcAutoConfiguration中有所不同
新版本中
@Bean
@ConditionalOnMissingBean(HiddenHttpMethodFilter.class)
@ConditionalOnProperty(prefix = "spring.mvc.hiddenmethod.filter", name = "enabled", matchIfMissing = false)
public OrderedHiddenHttpMethodFilter hiddenHttpMethodFilter() {
return new OrderedHiddenHttpMethodFilter();
}
老版本中
@Bean
@ConditionalOnMissingBean(HiddenHttpMethodFilter.class)
public OrderedHiddenHttpMethodFilter hiddenHttpMethodFilter() {
return new OrderedHiddenHttpMethodFilter();
}
老版本中生效 而新版本不生效
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
HttpServletRequest requestToUse = request;
if ("POST".equals(request.getMethod()) && request.getAttribute(WebUtils.ERROR_EXCEPTION_ATTRIBUTE) == null) {
String paramValue = request.getParameter(this.methodParam);
if (StringUtils.hasLength(paramValue)) {
String method = paramValue.toUpperCase(Locale.ENGLISH);
if (ALLOWED_METHODS.contains(method)) {
requestToUse = new HttpMethodRequestWrapper(request, method);
}
}
}
filterChain.doFilter(requestToUse, response);
}
spring 2.2.5解决办法:
/** 第一种 先调用获取参数 解决getParameterNames 和getInputStream 冲突问题 **/
Map<String, String[]> parameterMap = request.getParameterMap();
log.info("请求参数:{}", JSON.toJSONString(parameterMap));
if (request instanceof HttpServletRequest) {
// 将请求对象包装为 可重复读取流的请求对象。注意:构造好了,但是需要在拦截器中获取
requestWrapper = new BodyReaderHttpServletRequestWrapper((HttpServletRequest) request);
chain.doFilter(requestWrapper, response);
}else {
chain.doFilter(request, response);
}
return;
参考文档
Request重复读取流 - 简书 (jianshu.com)
记一次getParameter()获取不到参数问题的排查 - litter-chick - 博客园 (cnblogs.com)