最近打算在之前的项目APP接口里面加入验证签名的功能,实现思路很简单,就是通过添加filter的方式,在filter中读取所有的请求参数,然后验证客户端传过来的SIGN值跟服务端生成的SIGN值是否一致。
目前的接口里面参数有K=V格式的,也有JSON格式的,对于前者在filter中通过HttpServletRequest.getParameterMap就可以直接获取。但是对于JSON参数,我们需要从request的inputStream中读取,当然这也不复杂,几行代码就可以搞定了。
但是当我完成filter中的代码,在进行调试的时候,发现原来controller中通过@RequestBody获取JSON参数的接口抛出Required request body is missing
的错误。刚开始还以为参数传的有问题,查阅相关资料才明白inputStream的数据只能读取一次,从inputStream中读取过数据之后,后续再从inputStream中就不能再读取到数据了。
@Slf4j
@WebFilter(filterName = "myFilter", urlPatterns = {"/*"}, initParams = {@WebInitParam(name = "author", value = "Jaemon")})
public class MyFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
log.info("过滤器初始化={}", filterConfig.getFilterName());
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
log.info("执行过滤器...");
// 对 request 和 response 进行一些预处理
servletRequest.setCharacterEncoding("UTF-8");
servletResponse.setCharacterEncoding("UTF-8");
servletResponse.setContentType("text/html;charset=UTF-8");
// 此处读取了 inputStream
String body = IOUtils.toString(servletRequest.getInputStream(), servletRequest.getCharacterEncoding());
log.info("request body={}", body);
// 做一些业务相关的请求验证处理, 如: 验签
filterChain.doFilter(servletRequest, servletResponse);
}
@Override
public void destroy() {
log.info("过滤器销毁");
}
}
@Slf4j
@WebFilter(filterName = "myFilter", urlPatterns = {"/*"}, initParams = {@WebInitParam(name = "author", value = "Jaemon")})
public class MyFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
log.info("过滤器初始化={}", filterConfig.getFilterName());
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
// 封装类, 将 inputStream 读取到 body 中之后再 写到 inputStream 中
BodyCachingRequestWrapper requestWrapper = new BodyCachingRequestWrapper((HttpServletRequest) servletRequest);
log.info("执行过滤器...");
// 对 request 和 response 进行一些预处理
servletRequest.setCharacterEncoding("UTF-8");
servletResponse.setCharacterEncoding("UTF-8");
servletResponse.setContentType("text/html;charset=UTF-8");
String body = new String(requestWrapper.getBody(), servletRequest.getCharacterEncoding());
log.info("request body={}", body);
// 做一些业务相关的请求验证处理, 如: 验签
filterChain.doFilter(requestWrapper, servletResponse);
}
@Override
public void destroy() {
log.info("过滤器销毁");
}
}
public class BodyCachingRequestWrapper extends HttpServletRequestWrapper {
private byte[] body;
private BufferedReader reader;
private ServletInputStream inputStream;
public BodyCachingRequestWrapper(HttpServletRequest request) throws IOException {
super(request);
loadBody(request);
}
private void loadBody(HttpServletRequest request) throws IOException {
// 读取 inputStream 中的内容到 body 之后,再将数据写回 inputStream 中
body = IOUtils.toByteArray(request.getInputStream());
inputStream = new RequestCachingInputStream(body);
}
public byte[] getBody() {
return body;
}
@Override
public ServletInputStream getInputStream() throws IOException {
if (inputStream != null) {
return inputStream;
}
return super.getInputStream();
}
@Override
public BufferedReader getReader() throws IOException {
if (reader == null) {
reader = new BufferedReader(new InputStreamReader(inputStream, getCharacterEncoding()));
}
return reader;
}
private static class RequestCachingInputStream extends ServletInputStream {
private final ByteArrayInputStream inputStream;
public RequestCachingInputStream(byte[] bytes) {
inputStream = new ByteArrayInputStream(bytes);
}
@Override
public int read() throws IOException {
return inputStream.read();
}
@Override
public boolean isFinished() {
return inputStream.available() == 0;
}
@Override
public boolean isReady() {
return true;
}
@Override
public void setReadListener(ReadListener readlistener) {
}
}
}
在SpringBootApplication上使用@ServletComponentScan注解后,Servlet、Filter、Listener可以直接通过@WebServlet、@WebFilter、@WebListener注解自动注册,无需其他代码。