场景交代

在springboot中添加拦截器进行权限拦截时,需要获取请求参数进行验证。当参数在url后面时(queryString)获取参数进行验证之后程序正常运行。但是,当请求参数在请求体中的时候,通过流的方式将请求体取出参数进行验证之后,发现后续流程抛出错误:

Required request body is missing ...

经过排查,发现ServletInputStream的流只能读取一次(参考:httpServletRequest中的流只能读取一次的原因)。

这就是为什么在拦截器中读取消息体之后,controller的@RequestBody注解无法获取参数的原因。

解决思路

既然知道了原因,那就可以想到一个大概思路了:可不可以把请求的body流换成可重复读的流?

答案是可以的。可以通过继承HttpServletRequestWrapper类进行。

解决方案

1. 继承HttpServletRequestWrapper

继承HttpServletRequestWrapper类,将请求体中的流copy一份出来,覆写getInputStream()和getReader()方法供外部使用。如下,每次调用覆写后的getInputStream()方法都是从复制出来的二进制数组中进行获取,这个二进制数组在对象存在期间一直存在,这样就实现了流的重复读取。

import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStreamReader; import javax.servlet.ReadListener;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper; import org.springframework.util.StreamUtils; /**
*
* 从请求体中获取参数请求包装类:<br>
* @author nick
* @version 5.0 since 2018年9月5日
*/
public class BodyReaderHttpServletRequestWrapper extends HttpServletRequestWrapper{ private byte[] requestBody = null;//用于将流保存下来 public BodyReaderHttpServletRequestWrapper(HttpServletRequest request) throws IOException {
super(request);
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()));
}
}
2. 替换原始request对象

现在可重复读取流的请求对象构造好了,但是需要在拦截器中获取,就需要将包装后的请求对象放在拦截器中。由于filter在interceptor之前执行,因此可以通过filter进行实现。

创建filer,在filter中对request对象用包装后的request替换。

import java.io.IOException;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest; import com.znz.dns.controller.interceptor.auth.BodyReaderHttpServletRequestWrapper; @WebFilter(filterName="bodyReaderFilter",urlPatterns="/*")
public class BodyReaderFilter implements Filter{ @Override
public void init(FilterConfig filterConfig) throws ServletException {
// do nothing
} @Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
ServletRequest requestWrapper=null;
if(request instanceof HttpServletRequest) {
requestWrapper=new BodyReaderHttpServletRequestWrapper((HttpServletRequest)request);
}
if(requestWrapper==null) {
chain.doFilter(request, response);
}else {
chain.doFilter(requestWrapper, response);
} } @Override
public void destroy() {
// do nothing } }

配置filter

@Bean
public FilterRegistrationBean<BodyReaderFilter> Filters() {
FilterRegistrationBean<BodyReaderFilter> registrationBean = new FilterRegistrationBean<BodyReaderFilter>();
registrationBean.setFilter(new BodyReaderFilter());
registrationBean.addUrlPatterns("/*");
registrationBean.setName("koalaSignFilter");
return registrationBean;
}
3. 获取请求体

既然request对象流已经换成了wrapper reqest,那么流就可以重复读取了。接下来就是获取。

在拦截器中,直接从request中获取流,并进行读取:

/**
* 获取请求体内容
* @return
* @throws IOException
*/
private Map<String, Object> getParamsFromRequestBody(HttpServletRequest request) throws IOException {
BufferedReader reader = request.getReader(); StringBuilder builder = new StringBuilder();
try {
String line = null;
while((line = reader.readLine()) != null) {
builder.append(line);
}
String bodyString = builder.toString();
return objectMapper.readValue(bodyString, Map.class);
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
reader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
return new HashMap<>();
}

补充

在网上找了一些关于“springboot请求体中流不可重复读取问题”的相关文章,解决了自己的问题,但是觉得整个逻辑不怎么清晰(可能是自己没理解?) 因此,根据自己的思路重新整理了一遍。

参考文章

springboot请求体中的流只能读取一次的问题的更多相关文章

  1. httpServletRequest中的流只能读取一次的原因

    首先,我们复习一下InputStream read方法的基础知识, java InputStream read方法内部有一个,postion,标志当前流读取到的位置,每读取一次,位置就会移动一次,如果 ...

  2. 解决SpringMVC拦截器中Request数据只能读取一次的问题

    解决SpringMVC拦截器中Request数据只能读取一次的问题 开发项目中,经常会直接在request中取数据,如Json数据,也经常用到@RequestBody注解,也可以直接通过request ...

  3. 如何对POST请求但是URL中也有参数/GET请求但是请求体中也有参数的情况进行安全扫描

    通常情况下,GET的参数都在URL中,POST的参数都在请求体中,但是如题的情况也有,像使用方法PUT.DELETE的情况也有,这些情况该如何进行安全扫描呢?

  4. request.getInputStream() 流只能读取一次问题

    问题: 一次开发过程中同事在 sptring interceptor 中获取 request body 中值,以对数据的校验和预处理等操作 .导致之后spring 在读取request body 值做 ...

  5. 微信小程序:post请求参数放在请求体中还是拼接到URL中需要看后台是如何接收的

    前端发送post请求时,请求参数可以放在请求中,代码如下: function post(url, data, callback) { wx.request({ method: 'POST', url: ...

  6. 利用Filter和HttpServletRequestWrapper实现请求体中token校验

    先说一下项目的背景,系统传参为json格式,token为其中一个必传参数,此时如果在过滤器中直接读取request,则后续controller中通过RequestBody注解封装请求参数是会报stre ...

  7. Java中IO流文件读取、写入和复制

    //构造文件File类 File f=new File(fileName); //判断是否为目录 f.isDirectory(); //获取目录下的文件名 String[] fileName=f.li ...

  8. 请求体中需要的true和requests包put请求冲突了

    python  put请求,添加请求头 不知道怎么解决

  9. 使用restTemplate发送post请求,传入参数是在requestBody请求体中,以json形式传输

    @PostMapping public ResponseResult add(User user){ HttpHeaders httpHeaders = new HttpHeaders(); Medi ...

随机推荐

  1. Tomcat 目录结构

    bin目录主要是用来存放tomcat的命令,主要有两大类,一类是以.sh结尾的(linux命令),另一类是以.bat结尾的(windows命令). 很多环境变量的设置都在此处,例如可以设置JDK路径. ...

  2. NDK学习笔记-JNI的异常处理与缓存策略

    在使用JNI的时候,可能会产生异常,此时就需要对异常进行处理 异常处理 JNI抛出Throwable异常,在Java层可以用Throwable捕捉 而在C只有清空异常这种处理 但如果在JNI中通过Th ...

  3. ActiveMQ单机部署及简单应用

    系统版本:Centos 7 前言 MQ是消息中间件,是一种在分布式系统中应用程序借以传递消息的媒介,常用的有ActiveMQ,RabbitMQ,kafka.ActiveMQ是Apache下的开源项目, ...

  4. Linux安装zookeeper集群

    一.单机部署: ①下载.解压 http://archive.apache.org/dist/zookeeper/ tar -zxf zookeeper-3.4.10.tar.gz -C /usr/lo ...

  5. CentOS7.0 测试环境准备过程

    1. 公司最近开始走向国产化和开源路线,因为绝大多数国产化都是采取的linux路线, 所以为了保证兼容性, 测试环境大部分从windows迁移到了linux环境. 测试采取逐步推进的模式, 先测试兼容 ...

  6. [转载]ASP.NET Core文件上传与下载(多种上传方式)

    ASP.NET Core文件上传与下载(多种上传方式)   前言 前段时间项目上线,实在太忙,最近终于开始可以研究研究ASP.NET Core了. 打算写个系列,但是还没想好目录,今天先来一篇,后面在 ...

  7. base64转码java版

    package com.net.util; import java.io.FileInputStream; import java.io.FileOutputStream; import java.i ...

  8. cmake 简单build和删除

    mkdir build cd build cmake .. make

  9. 关于JS原型以及原型链、instanceof的一些理解

    一.JS原型 首先要区分两个概念 1.构造函数 2.实例:由构造函数通过new方式创建出来的就是实例 <script> function Foo() { } var f = new Foo ...

  10. vue开发环境配置跨域,一步到位

    本文要实现的是:使用vue-cli搭建的项目在开发时配置跨域,上线后不做任何任何修改,接口也可以访问,前端跨域解决方案 production:产品 生产环境 development:开发 开发环境 1 ...