场景交代

在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. 《剑指offer》Q13-18 (牛客10.13)

    目录 Q13 调整数组顺序使奇数位于偶数前 Q14 链表中倒数第k个结点 Q15 反转链表 Q16 合并两个有序链表 Q17 树的子结构 Q18 二叉树的镜像 Q13 调整数组顺序使奇数位于偶数前 输 ...

  2. 【计算机视觉】双目测距(六)--三维重建及UI显示

    原文: http://blog.csdn.NET/chenyusiyuan/article/details/5970799 在获取到视差数据后,利用 OpenCV 的 reProjectImageTo ...

  3. orcale数据库授权码

    Product Code:4t46t6vydkvsxekkvf3fjnpzy5wbuhphqzserial Number:601769password:xs374ca

  4. Ubuntu18突然卡死解决方法

    emmmm 1.Ctrl+Alt+F2/F3/F4/F5/F6     F2-6随便选一个都可以 2.进入tty终端后先输入用户名和密码(记得小键盘会自动

  5. DP单调队列--斜率优化P3195

    题意:https://www.luogu.com.cn/problem/P3195 思路:https://www.luogu.com.cn/problemnew/solution/P3195 #def ...

  6. PHP和js判断访问设备是否是微信浏览器实例

    PHP和js判断访问设备是否是微信浏览器实例,代码非常精简,适合新手学习. js判断是否是微信浏览器: 1 function is_weixin() { 2 var ua = window.navig ...

  7. 基于TCP 协议的socket 简单通信

    DNS 服务器:域名解析 socket 套接字 : ​ socket 是处于应用层与传输层之间的抽象层,也是一组操作起来非常简单的接口(接受数据),此接口接受数据之后,交由操作系统 为什么存在 soc ...

  8. 8.perf top系统性能分析工具

    perf 是一个调查 Linux 中各种性能问题的有力工具. # perf --help  usage: perf [--version] [--help] COMMAND [ARGS]  The m ...

  9. DevExpress WPF控件记录

    以下是博主用到DevExpress WPF控件时的一些记录笔记: 1.Canvas控件:Canvas控件的背景色一定要设置(background="Transparent"),不然 ...

  10. Linux:删除一个目录下的所有文件,但保留一个指定文件

    面试题:删除一个目录下的所有文件,但保留一个指定文件 解答: 假设这个目录是/xx/,里面有file1,file2,file3..file10  十个文件 [root@oldboy xx]# touc ...