第三方调用安全校验
1. 拦截器代码
/** * @Description 添加请求是否合法验证拦截器 * @author 田林(lin.tian@mljr.com) * @date 2017年12月1日 下午4:20:38 */ @Component("signature") public class SignatureFilter implements Filter { private Logger logger = LoggerFactory.getLogger(SignatureFilter.class); @Value("${rsaKey}") private String rsaKey; public final static String JSON_PARAMS_TYPE="application/json"; public final static String FORM_PARAMS_TYPE="application/x-www-form-urlencoded"; @Override public void init(FilterConfig filterConfig) throws ServletException { } @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { Map<String,String> singleValueMap = Maps.newHashMap(); HttpServletRequest httpRequest=(HttpServletRequest)request; String password = httpRequest.getHeader("password"); // 是否通过验证 boolean flag = false; //时间戳 String timestamp = httpRequest.getHeader("timestamp"); String serverPath = httpRequest.getServletPath(); if("/".equals(serverPath)||serverPath.contains("/fe-che-union/static")){ chain.doFilter(request,response); }else{ HttpServletResponse httpResponse= (HttpServletResponse) response; String contentType = httpRequest.getContentType(); if(!StringUtils.isEmpty(contentType)&&contentType.contains(JSON_PARAMS_TYPE)){//如果是json请求方式 SignatureRequestWrapper requestWrapper = new SignatureRequestWrapper(httpRequest); String body = HttpHelper.getBodyString(requestWrapper); if (StringUtils.isEmpty(body)) { logger.error("非法请求, 无参数"); OutWriterUtil.write(httpResponse, JSONObject.toJSONString(RespDTO.fail("无参数"))); return; } Map<String, Object> parameters = JSONObject.parseObject(body); Set<String> keySet = parameters.keySet(); for(String key:keySet){ singleValueMap.put(key, JSONObject.toJSONString(parameters.get(key))); } request=requestWrapper; }else if(!StringUtils.isEmpty(contentType)&&contentType.contains(FORM_PARAMS_TYPE)){ Map<String,String[]> parameterMap = request.getParameterMap(); for(String key : parameterMap.keySet()){ String[] valueArray = parameterMap.get(key); if(valueArray!=null&&valueArray.length>0){ singleValueMap.put(key, valueArray[0]); } } } else { flag = true; } if(flag){ chain.doFilter(request,response); }else{ // 校验参数合法性 flag = SignatureUtils.checkSign(singleValueMap, rsaKey,password,timestamp); if(flag){ chain.doFilter(request,response); }else{ OutWriterUtil.write(httpResponse, JSONObject.toJSONString(RespDTO.fail("签名错误必须存在"))); return; } } } } private boolean checkSign(Map<String, Object> parameters) { Map<String, String> requestParams=new HashMap<>(); SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); for (String key : parameters.keySet()) { String valueStr=""; Object value = parameters.get(key); if (value ==null){ continue; } if(BeanUtils.isSimpleValueType(value.getClass())){ //如果是简单类型 if(Date.class.isAssignableFrom(value.getClass())){//如果是时间类型 valueStr = dateFormat.format(value); }else{ valueStr=value.toString(); } }else { //如果是复杂类型 valueStr=JSONObject.toJSONString(value); } requestParams.put(key,valueStr); } return SignUtils.checkSign(requestParams,rsaKey); } private boolean checkParamIsExist(Map<String, Object> parameters, String... keys) { for (String key: keys) { if(parameters.get(key)==null){return false;} } return true; } @Override public void destroy() { } }
2. 对输入流进行封装:
public class SignatureRequestWrapper extends HttpServletRequestWrapper { private HttpServletRequest original; private byte[] reqBytes; private boolean firstTime = true; public SignatureRequestWrapper(HttpServletRequest request) { super(request); reqBytes = HttpHelper.getBodyString(request).getBytes(Charset.forName("UTF-8")); } @Override public BufferedReader getReader() throws IOException{ InputStreamReader isr = new InputStreamReader(new ByteArrayInputStream(reqBytes)); return new BufferedReader(isr); } @Override public ServletInputStream getInputStream() throws IOException { ServletInputStream sis = new ServletInputStream() { @Override public boolean isFinished() { return false; } @Override public boolean isReady() { return false; } @Override public void setReadListener(ReadListener readListener) { } private int i; @Override public int read() throws IOException { byte b; if(reqBytes.length > i){ b = reqBytes[i++]; }else{ b = -1; } return b; } }; return sis; } }
3. 校验代码
public class SignUtils { /** * 拼接键值对 * * @param key * @param value * @param isEncode * @return */ private static String buildKeyValue(String key, String value, boolean isEncode) { StringBuilder sb = new StringBuilder(); sb.append(key); sb.append("="); if (isEncode) { try { sb.append(URLEncoder.encode(value, "UTF-8")); } catch (UnsupportedEncodingException e) { sb.append(value); } } else { sb.append(value); } return sb.toString(); } /** * 对支付参数信息进行签名 * * @param map * 待签名授权信息 * * @return */ public static String sign(Map<String, String> map, String rsaKey) { StringBuilder sortStr = getSortStr(map); StringBuilder authInfo = getAuthInfo(rsaKey, sortStr); String sign = EncryptUtil.MD5(authInfo.toString()); return sign; } private static StringBuilder getAuthInfo(String rsaKey, StringBuilder sortStr) { StringBuilder authInfo=new StringBuilder(); authInfo.append(rsaKey); authInfo.append(sortStr); authInfo.append(rsaKey); return authInfo; } private static StringBuilder getSortStr(Map<String, String> map) { List<String> keys = new ArrayList<String>(map.keySet()); // key排序 Collections.sort(keys); StringBuilder authInfo = new StringBuilder(); for (int i = 0; i < keys.size() - 1; i++) { String key = keys.get(i); String value = map.get(key); authInfo.append(buildKeyValue(key, value, false)); authInfo.append("&"); } String tailKey = keys.get(keys.size() - 1); String tailValue = map.get(tailKey); authInfo.append(buildKeyValue(tailKey, tailValue, false)); return authInfo; } /** * 要求外部订单号必须唯一。 * @return */ public static boolean checkSign(Map<String, String> map, String rsaKey) { String password=map.remove("password"); StringBuilder sortStr = getSortStr(map); StringBuilder authInfo = getAuthInfo(rsaKey, sortStr); return EncryptUtil.checkPassWord(authInfo.toString(),password); }
4. Filter 无法直接通过 @Value 注入properties属性 ,可以通过 DelegatingFilterProxy 来处理,将Filter 交给spring来管理。web.xml 配置:
<filter> <filter-name>signature</filter-name> <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class> </filter> <filter-mapping> <filter-name>signature</filter-name> <url-pattern>/*</url-pattern> </filter-mapping>