请求参数完整性校验,解决流只能写一次的问题
背景:一个app的后台开发,现在想要对请求内容进行校验,防止请求内容被人篡改,目前的做法是前端发送请求时,对参数进行MD5摘要算法(中间加了些我们自己约定的规则),将算出来的MD5值附在请求里一起带过来,后台拿着前台传过来的参数进行相同规则的计算,将计算出来的MD5值和前台计算的值进行比较。
想法没什么问题,然而开发时发现在拦截器里获取到的已经是mapper后的对象了,拿不到原始请求的json串,自然也不好比较算出来的MD5值了。
查了下,前台是post请求数据是json类型,那request.getparameter()之类的就拿不到值了,要获得原始请求的json串,只能去流里面读了,那流读完一次就没法读了,最开始的想法是能不能在读流的地方,把原始请求留个备份啥的。debug跟了下整个请求处理的源码(spring 那一套),找到最终是在com.fasterxml.jackson.databind包里的ObjectMapper类里面去解析了流,并转成对应的对象。这是jackson-databind jar包的方法,
修改重打jar包肯定不现实,想了下本地建个同名的包,再建个同名的ObjectMapper类,自然就可以把原来jar包里的ObjectMapper类给覆盖掉了,试了下,可行。姑且算方案一吧。但这方法还是不太好,要建一个与项目不相干的包。
方案二,那我能想到的还是得想办法解决流只能读一次这个问题了,那就得想办法是不是可以把流再写回去,查了下,参考了网上的办法。
也就是在通过加过滤器,在过滤器里,把流读出来后,通过重写HttpServletRequestWrapper的getInputStream的方法,再把流的内容写回去。代码网上很多,大同小异。
public class BodyReaderHttpServletRequestWrapper extends HttpServletRequestWrapper { private final byte[] body; public BodyReaderHttpServletRequestWrapper(HttpServletRequest request) throws IOException { super(request); body = getBodyString(request).getBytes(Charset.forName("UTF-8")); } @Override public BufferedReader getReader() throws IOException { return new BufferedReader(new InputStreamReader(getInputStream())); } @Override public ServletInputStream getInputStream() throws IOException { final ByteArrayInputStream bais = new ByteArrayInputStream(body); 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) { } }; } public static String getBodyString(ServletRequest request) { try { return StreamUtil.readText(request.getInputStream()); } catch (IOException e1) { throw ReqErrCodes.CUSTOM_PROMPT.exception("流内容解析失败"); } } }
@Order(1) @WebFilter(filterName = "checkSignFilter", urlPatterns = "/*") public class CheckSignFilter implements Filter { @Override public void init(FilterConfig filterConfig) throws ServletException { } @Override public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { System.out.println("CheckSignFilter"); HttpServletRequest httpRequest = (HttpServletRequest) servletRequest; ServletRequest requestWrapper = new BodyReaderHttpServletRequestWrapper(httpRequest); String str = BodyReaderHttpServletRequestWrapper.getBodyString(requestWrapper); HttpServletRequest request = null; if(servletRequest instanceof HttpServletRequest){ request = (HttpServletRequest)servletRequest; } String md5code = request.getHeader("MD5Code"); String MD5ecode = MD5.getMessageDigest(str); if(!md5code.equals(MD5ecode)){ // TODO } filterChain.doFilter(requestWrapper, servletResponse); } @Override public void destroy() { } }