springboot配置过滤器导致的controller入参丢失问题
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)
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· Docker 太简单,K8s 太复杂?w7panel 让容器管理更轻松!