不知道你们有没有对用户输入的东西进行过敏感校验,如果不进行校验,用户属于一些攻击脚本,那么我们的服务就挂逼啦!所以我们首先需要通过过滤器将用户的数据读出来进行安全校验,这里面涉及到一个动作,就是需要将用户的数据在过滤器中读出来,进行校验,通过之后再放行。
问题
如果我们的数据是 get 请求倒还好,但是如果是一些数据量比较大,我们需要通过 post json 的方式来说传递数据的时候,这个时候其实是通过流的方式传递的,如果在过滤器中将参数读取出来之后,然后放行,等到到 Servlet 的时候,@RequestBody 是无法获取到数据的, 因为 post json 使用流传递, 流被读取之后就不存在了, 所以我们在过滤器中读取之后,@ReqeustBody 自然就读不到数据了,同时会报如下一个错误。
- 在过滤器中读取 body 中的数据
@WebFilter
@Slf4j
public class CheckUserFilter implements Filter {
@Override
public void doFilter(ServletRequest req, ServletResponse res, FilterChain filterChain) throws IOException, ServletException {HttpServletRequest request = (HttpServletRequest) req;
// 在过滤器中读取数据
BufferedReader reader = request.getReader();
StringBuilder sb = new StringBuilder();
String line;
while ((line = reader.readLine()) != null) {sb.append(line);
}
reader.close();
System.out.println(sb.toString());
filterChain.doFilter(request, res);
}
}
- 出现异常,就是说内容已经被读取了,你不能调用了
{"id":"1", "username":"bingfeng"}
java.lang.IllegalStateException: UT010003: Cannot call getInputStream(), getReader() already called
at io.undertow.servlet.spec.HttpServletRequestImpl.getInputStream(HttpServletRequestImpl.java:666)
at javax.servlet.ServletRequestWrapper.getInputStream(ServletRequestWrapper.java:152)
at javax.servlet.ServletRequestWrapper.getInputStream(ServletRequestWrapper.java:152)
解决
- HttpServletRequestWrapper
那么出现这种问题怎么办呢?能不能通过一个中间的变量将这些数据保存下来,然后我们就可以一直读取了,这样不就解决了这个问题了吗?那保存在哪里呢?这个时候 HttpServletRequestWrapper 就排上用场了。
这个其实你可以把它理解为 Request 的包装类,Reqeust 中有的方法它都有,我们通过继承这个类,重写该类中的方法,将 body 中的参数保存一个 byte 数组中,然后放行的时候将这个包装类传递进去,不就可以一直拿到参数了?
- 封装 Request 类
public class BodyReaderHttpServletRequestWrapper extends HttpServletRequestWrapper {private final byte[] body;
/**
* 所有参数的集合
*/
private Map<String, String[]> parameterMap;
public BodyReaderHttpServletRequestWrapper(HttpServletRequest request) throws IOException {super(request);
BufferedReader reader = request.getReader();
body = readBytes(reader);
parameterMap = request.getParameterMap();}
@Override
public BufferedReader getReader() throws IOException {ServletInputStream inputStream = getInputStream();
if (null == inputStream) {return null;}
return new BufferedReader(new InputStreamReader(inputStream));
}
@Override
public Enumeration<String> getParameterNames() {Vector<String> vector = new Vector<>(parameterMap.keySet());
return vector.elements();}
@Override
public ServletInputStream getInputStream() throws IOException {if (body == null) {return null;}
final ByteArrayInputStream bais = new ByteArrayInputStream(body);
return new ServletInputStream() {
@Override
public boolean isFinished() {return false;}
@Override
public boolean isReady() {return false;}
@Override
public void setReadListener(ReadListener listener) { }
@Override
public int read() throws IOException {return bais.read();
}
};
}
/**
* 通过 BufferedReader 和字符编码集转换成 byte 数组
*
* @param br
* @return
* @throws IOException
*/
private byte[] readBytes(BufferedReader br) throws IOException {
String str;
StringBuilder retStr = new StringBuilder();
while ((str = br.readLine()) != null) {retStr.append(str);
}
if (StringUtils.isNotBlank(retStr.toString())) {return retStr.toString().getBytes(StandardCharsets.UTF_8);
}
return null;
}
}
- 将过滤器改造
@WebFilter
@Slf4j
public class CheckUserFilter implements Filter {
@Override
public void doFilter(ServletRequest req, ServletResponse res, FilterChain filterChain) throws IOException, ServletException {HttpServletRequest request = (HttpServletRequest) req;
BodyReaderHttpServletRequestWrapper requestWrapper = new BodyReaderHttpServletRequestWrapper(request);
// 从 Request 的包装类中读取数据
BufferedReader reader = requestWrapper.getReader();
StringBuilder sb = new StringBuilder();
String line;
while ((line = reader.readLine()) != null) {sb.append(line);
}
reader.close();
System.out.println(sb.toString());
filterChain.doFilter(requestWrapper, res);
}
}
经过这样的配置之后,我们即使在过滤器中获取了参数,请求也会到达 Servlet。
如果基础知识 IO 那块不是很扎实的话,第一眼看到这个问题确实挺懵逼的。我也是百度之后解决的,确实值得记录一下,有时候我们会对所有请求进来的参数进行保存输出什么的,这个时候如果是 post json 数据的话,如果不是特别明白,可能也会出现这种问题。
<p style=”text-align:center;font-weight:bold;color:#0e88eb;font-size:20px”> 日拱一卒, 功不唐捐 </p>
<p style=”text-align:center;font-weight:bold;color:#773098;font-size:16px”> 更多内容请关注 </p>