场景交代

在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. Fastjson反序列化漏洞

    payload: 1.{"@type":"com.sun.rowset.JdbcRowSetImpl","dataSourceName":& ...

  2. Java学习笔记-抽象类与接口

    抽象类用于在类中不用具体实现,而在子类中去实现的类 抽象类 抽象类概述 抽象定义:抽象就是从多个事物中将共性的,本质的内容抽取出来 抽象类:Java中可以定义没有方法体的方法,该方法的具体实现由子类完 ...

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

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

  4. C语言课程设计

    目录 实现目的 游戏玩法介绍 实现流程与作品架构 任务列表及贡献度 总结感想 作品源码与仓库地址(附页) 资料引用与出处(附页) 实现目的 2048,作为一款极其经典的游戏,从发行到现在,已经有了极多 ...

  5. dubbo分布式服务框架-study2

    本文开始对springboot+dubbo集成,使用的版本为springboot 2.0.0.dubbo 2.0.0,步骤如下: 1.添加依赖: <dependency> <grou ...

  6. ubuntu下安装navicat

    1.去官网下载 https://www.navicat.com/en/download/navicat-premium 2.命令行输入(解压命令) tar -zxvf xxxxx.tar.gz 3.移 ...

  7. 网络编程之异步IO

    Linux的I/O模型有下面几种:1. 同步阻塞I/O: 用户进程进行I/O操作,一直阻塞到I/O操作完成为止.2. 同步非阻塞I/O: 用户程序可以通过设置文件描述符的属性O_NONBLOCK,I/ ...

  8. Unable to find optional library: org.apache.http.legacy 错误

    在目录adt-bundle-windows-x86_64-20140702\sdk\platforms\android-20或者 C:\Users\Administrator\AppData\Loca ...

  9. 牛客 216D 消消乐 (二分图最小点覆盖)

    大意: 给定棋盘, 每次消除一行或一列, 求最小次数使得消除完所有'*'. 裸的二分图最小点覆盖. 二分图的最小点覆盖等于最大匹配, 输出方案时从所有左部未盖点开始标记交替路上的点, 最后左部所有未标 ...

  10. 我的第一个python web开发框架(2)——第一个Hello World

    小白中午听完老菜讲的那些话后一直在思考,可想来想去还是一头雾水,晕晕呼呼的一知半解,到最后还是想不明白,心想:老大讲的太高深了,只能听懂一半半,看来只能先记下来,将明白的先做,不明白的等以后遇到再学. ...