最近的一个项目前后端分离,然后后端自定义Filter,通过继承org.springframework.web.filter.OncePerRequestFilter实现在请求到达Controller层之前进行统一的参数验签(MD5+盐)和token验证。其中,在进行验签时,需要获取body中的JSON数据,这时想着直接通过HttpServletRequest#getInputStream() 获取到请求的输入流,从该输入流中可以读取到请求体。但这里就有个坑了,这个流在被我们的代码 read 过后,之后的代码就会报错,因为流已经被我们读取过了 , 尝试使用 mark() , reset() 也是不行的,会抛出异常,这时,可以通过将 HttpServletRequest 对象包装一层的方式来实现这个功能。Servlet规范中也有相关说明:IO都是只能读取一次的,除非做了拷贝!也就是做了缓存!一个重要的概念就是Scope与Single Thread Model。
一. HttpServletRequestWrapper
通过继承HttpServletRequestWrapper类以实现在Filter中修改HttpServletRequest的参数。
- UML结构图
这里其实是应用了装饰器模式对HttpServletRequest进行了增强。 - ServletRequest 抽象组件
- HttpServletRequest 抽象组件的一个子类,它的实例被称作“被装饰者”
- ServletRequestWrapper 一个基本的装饰类,这里是非抽象的
- HttpServletRequestWrapper 一个具体的装饰者,当然这里也继承了HttpServletRequest这个接口,是为了获取一些在ServletRequest中没有的方法
二.代码实现
- 自定义Wrapper
package com.hong.security.common;
import org.springframework.util.StreamUtils;
import javax.servlet.ReadListener;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
/**
* @author wanghong
* @date 2020/05/11 22:47
**/
public class WrappedRequest extends HttpServletRequestWrapper {
private byte[] requestBody = null;
public WrappedRequest(HttpServletRequest request) {
super(request);
// 缓存请求body
try {
requestBody = StreamUtils.copyToByteArray(request.getInputStream());
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
public ServletInputStream getInputStream() throws IOException {
if (requestBody == null) {
requestBody = new byte[0];
}
final ByteArrayInputStream bais = new ByteArrayInputStream(requestBody);
return new ServletInputStream() {
@Override
public int read() throws IOException {
return bais.read();
}
@Override
public boolean isFinished() {
// TODO Auto-generated method stub
return true;
}
@Override
public boolean isReady() {
// TODO Auto-generated method stub
return true;
}
@Override
public void setReadListener(ReadListener listener) {
// TODO Auto-generated method stub
}
};
}
@Override
public BufferedReader getReader() throws IOException {
return new BufferedReader(new InputStreamReader(getInputStream()));
}
}
- Filter中获取请求体数据
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws IOException, ServletException {
System.out.println("JwtAuthenticationTokenFilter Begin");
ServletRequest requestWrapper = new WrappedRequest(request);
String requestParams = readJSONString(requestWrapper);
// 。。。
}
public String readJSONString(ServletRequest request) {
StringBuffer json = new StringBuffer();
String line = null;
try {
BufferedReader reader = request.getReader();
while ((line = reader.readLine()) != null) {
json.append(StringUtils.trim(line));
}
} catch (Exception e) {
log.info("readJSONString方法异常:[{}]", request, e);
}
return json.toString();
}
}
三. 修改HttpServletRequest的参数或请求头
package com.hong.security.common;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import java.util.Enumeration;
import java.util.Map;
import java.util.Vector;
/**
* @author wanghong
* @date 2020/05/19 10:50
* 继承HttpServletRequestWrapper,创建装饰类,以达到修改HttpServletRequest参数的目的
**/
public class ModifyParametersWrapper extends HttpServletRequestWrapper {
private Map<String, String[]> parameterMap; // 所有参数的Map集合
public ModifyParametersWrapper(HttpServletRequest request) {
super(request);
parameterMap = request.getParameterMap();
}
// 重写几个HttpServletRequestWrapper中的方法
/**
* 获取所有参数名
*
* @return 返回所有参数名
*/
@Override
public Enumeration<String> getParameterNames() {
Vector<String> vector = new Vector<>(parameterMap.keySet());
return vector.elements();
}
/**
* 获取指定参数名的值,如果有重复的参数名,则返回第一个的值 接收一般变量 ,如text类型
*
* @param name 指定参数名
* @return 指定参数名的值
*/
@Override
public String getParameter(String name) {
String[] results = parameterMap.get(name);
if (results == null || results.length <= 0)
return null;
else {
System.out.println("before modify: " + results[0]);
return modify(results[0]);
}
}
/**
* 获取指定参数名的所有值的数组,如:checkbox的所有数据
* 接收数组变量 ,如checkbox类型
*/
@Override
public String[] getParameterValues(String name) {
String[] results = parameterMap.get(name);
if (results == null || results.length <= 0)
return null;
else {
int length = results.length;
for (int i = 0; i < length; i++) {
System.out.println("before modify2: " + results[i]);
results[i] = modify(results[i]);
}
return results;
}
}
/**
* 自定义的一个简单修改原参数的方法,即:给原来的参数值前面添加了一个修改标志的字符串
*
* @param string 原参数值
* @return 修改之后的值
*/
private String modify(String string) {
return "Modified: " + string;
}
}
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws IOException, ServletException {
System.out.println("JwtAuthenticationTokenFilter Begin");
// @RequestParam参数修改示例
ModifyParametersWrapper mParametersWrapper = new ModifyParametersWrapper(request);
filterChain.doFilter(mParametersWrapper, response);
}
}
package com.hong.security.controller;
import com.hong.security.common.Result;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;
import java.util.Map;
/**
* @author wanghong
* @date 2020/05/12 17:27
* 个人账号
**/
@RestController
@RequestMapping("/account")
public class AccountController {
@PostMapping("/modify")
public void modify(@RequestParam("name") String name) {
System.out.println("到达Controller层,after modify:" + name);
}
}
参考:https://blog.51cto.com/983836259/1877592
https://www.jianshu.com/p/a8c9d45775ea
https://juejin.im/post/5e6aee11e51d452703137ce6