spring mvc 添签验签方案
spring mvc 加签验签方案
一、请求格式要求:
1.GET 请求
URL示例:http://xxxxxxxx/api/test.do?a=1&b=2&c=3&d=4
content-type: | |
签名 | key值与a=1&b=2&c=3&d=4 进行一定处理后加密得到 |
2.POST请求
URL示例:http://xxxxxxxx/api/test.do
content-type: | application/json |
requestBody: | {a:1,b:'2',c:'3',d:'4'} |
签名 | key值与与{a:1,b:'2',c:'3',d:'4'} 进行一定处理后加密得到 |
二、签名参数:
通过第一步中得到签名值后,以参数名 “signature”添加到RequestHead中。
四、验签方案:
(1) 增加一个Filter,对POST请求从inputstream中获取json参数,放到ThreadLocal中,并替换掉原来的HttpServlertRequest,因为inputStream只能读一次,如果不替换,会导致spring将参数转换为JSON时没有数据可读。(对GET请求可以不作处理)
(2) 增加一个spring mvc 拦截器,从RequestHead中取出签名,与ThreadLocal中的参数进行验证。(GET请求直接从URL中取参数)
Filter代码如下:
@Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { MyHttpServletRequestWrapperwrap = null; if (request instanceof HttpServletRequest) { HttpServletRequest httpServletRequest = (HttpServletRequest) request; if ("POST".equals(httpServletRequest.getMethod().toUpperCase()) && httpServletRequest.getContentType().contains("application/json")) { wrap = new MyHttpServletRequestWrapper(httpServletRequest); //request = wrap; TokenBag.setParam(wrap.getJsonPararms()); } } if(null != wrap){ chain.doFilter(wrap, response); }else{ chain.doFilter(request, response); } }
MyHttpServletRequestWrapper:用来替换原来的request对象
public class MyHttpServletRequestWrapper extends HttpServletRequestWrapper { private String jsonPararms; public MyHttpServletRequestWrapper(HttpServletRequest request) throws IOException{ super(request); ServletInputStream stream = this.getRequest().getInputStream(); jsonPararms = IOUtils.toString(stream, "UTF-8"); } @Override public ServletInputStream getInputStream() { byte[] buffer = null; try { buffer = jsonPararms.toString().getBytes("UTF-8"); } catch (UnsupportedEncodingException e) { e.printStackTrace(); } final ByteArrayInputStream bais = new ByteArrayInputStream(buffer); ServletInputStream newStream = new ServletInputStream() { @Override public int read() throws IOException { return bais.read(); } }; return newStream; } public String getJsonPararms() { return jsonPararms; } public void setJsonPararms(String jsonPararms) { this.jsonPararms = jsonPararms; } }
mvc拦截器:
public class SignatureInterceptor extends HandlerInterceptorAdapter{ private Logger logger = Logger.getLogger(SignatureInterceptor.class); private TokenService tokenService; /** * This implementation always returns {@code true}. */ public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { if(logger.isDebugEnabled()){ logger.debug("check http request signature start..."); } HandlerMethod hm = (HandlerMethod)handler; //通过方法上的注解来标识哪些请求需要验签 Signature s = hm.getMethodAnnotation(Signature.class); if(null == s){ return true; } String signature = request.getHeader("signature"); if(StringUtils.isEmpty(signature)){ logger.error("signature is empty,check signature failed..."); response.setStatus(401); return false; } if("GET".equals(request.getMethod())){ String param = request.getQueryString(); if(checkSignature(signature,param,response)){ //验签通过 return super.preHandle(request,response,handler); }else{ response.setStatus(401); return false; } }else if("POST".equals(request.getMethod())){ //TokenBag就是一个ThreadLocal String param = TokenBag.getParam(); TokenBag.clean(); if(StringUtils.isEmpty(param)){ logger.error("param is empty,check signature failed..."); response.setStatus(401); return false; } if(checkSignature(signature,param,response)){ //验签通过 return super.preHandle(request,response,handler); }else{ response.setStatus(401); return false; } }else{ logger.error("signature only support POST and GET"); return false; } }
验签方法要与加签方法一致,可以通过app和服务端使用相同的key值加密,最好是使用每个用户惟一的、动态的key值(比如token)来做。