赞
踩
1、新建Certificate类
package com.wise.medical.common.utils; import org.apache.commons.codec.digest.DigestUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.UnsupportedEncodingException; import java.net.URLEncoder; import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; import java.text.MessageFormat; import java.util.*; /** * 实现开放API接口签名验证 * 参考链接:https://www.jianshu.com/p/ad410836587a */ public class Certificate { public static Logger logger = LoggerFactory.getLogger(LoggerFactory.class); /** * Certificate构造对象,存储签名相关的参数 */ private CertificateBuilder property; /** * 假设允许客户端和服务端最多能存在15分钟的时间差,同时追踪记录在服务端的nonce集合。 * 当有新的请求进入时, * 首先检查携带的timestamp是否在15分钟内,如超出时间范围,则拒绝, * 然后查询携带的nonce,如存在已有集合,则拒绝。否则,记录该nonce,并删除集合内时间戳大于15分钟的nonce * (可以使用redis的expire,新增nonce的同时设置它的超时失效时间为15分钟)。 */ private String timestamp; /** * 唯一的随机字符串,用来标识每个被签名的请求 */ private String nonce; /** * 签名 */ private String sign; public Certificate(CertificateBuilder builder) { this.property = builder; } /** * 获取最终请求参数列表 * 注意:外部不要将timestamp、nonce、accessKey和sign这4个参数添加到方法参数params集合中 */ public String getUrlParamsWithSign(final LinkedHashMap<String, String> params) { this.sign = getSign(params); //6、最终请求Url参数 params.put("timestamp", this.timestamp); params.put("nonce", this.nonce); params.put("sign", this.sign); params.put("token", property.token); List<String> urlParams = new ArrayList<>(); for (Map.Entry<String, String> entry : params.entrySet()) { String strParam = MessageFormat.format("{0}={1}", entry.getKey(), entry.getValue()); urlParams.add(strParam); } return String.join("&", urlParams); } /** * 获取签名 * 注意:外部不要将timestamp、nonce、accessKey和sign这4个参数添加到方法参数params集合中 */ public String getSign(final LinkedHashMap<String, String> params) { if (this.sign == null || "".equals(this.sign)) { this.sign = createSign(params); } return this.sign; } /** * 创建签名 * 注意:外部不要将timestamp、nonce、accessKey和sign这4个参数添加到方法参数params集合中 */ private String createSign(final LinkedHashMap<String, String> params) { //1、除去空值请求参数 LinkedHashMap<String, String> newRequestParams = removeEmptyParam(params); //2、按照请求参数名的字母升序排列非空请求参数(包含AccessKey),使用URL键值对的格式(即key1=value1&key2=value2…)拼接成字符串 newRequestParams.put("token", property.token); String strUrlParams = parseUrlString(newRequestParams); logger.info ("strUrlParams === "+strUrlParams); //3、生成当前时间戳timestamp=now和唯一随机字符串nonce=random strUrlParams = addUniqueParam(strUrlParams); logger.info("strUrlParams2 === "+strUrlParams); if (strUrlParams.indexOf ("&") == 0){ strUrlParams = strUrlParams.substring (1); } //4、最后拼接上Secretkey得到字符串stringSignTemp String stringSignTemp = MessageFormat.format("{0}&SecretKey={1}", strUrlParams, property.secretKey); logger.info("stringSignTemp === "+stringSignTemp); //5、对stringSignTemp进行MD5运算,并将得到的字符串所有字符转换为大写,得到sign值 return DigestUtils.md5Hex(stringSignTemp.getBytes(property.charset)).toUpperCase(); } /** * 移除空请求参数 */ private LinkedHashMap<String, String> removeEmptyParam(final LinkedHashMap<String, String> params) { LinkedHashMap<String, String> newParams = new LinkedHashMap<>(); if (params == null || params.size() <= 0) { return newParams; } for (String key : params.keySet()) { String value = params.get(key); if (value == null || value.equals("")){ continue; } newParams.put(key, value); } return newParams; } /** * 使用URL键值对的格式(即key1=value1&key2=value2…)将参数列表拼接成字符串 */ private String parseUrlString(final Map<String, String> requestMap) { List<String> keyList = new ArrayList<>(requestMap.keySet()); Collections.sort(keyList); List<String> entryList = new ArrayList<>(); for (String key : keyList) { String value = requestMap.get(key); // try { // value = URLEncoder.encode(value, property.charset.name()); // } catch (UnsupportedEncodingException e) { // e.printStackTrace(); // } entryList.add(MessageFormat.format("{0}={1}", key, value)); } return String.join("&", entryList); } /** * timestamp+nonce方案标识每个被签名的请求 */ private String addUniqueParam(final String urlParams) { if (property.requestTimestamp == null){ this.timestamp = String.valueOf(System.currentTimeMillis()); }else{ this.timestamp = property.requestTimestamp; } if (property.requestNonce == null){ this.nonce = UUID.randomUUID().toString().replaceAll("-", ""); } else{ this.nonce = property.requestNonce; } return MessageFormat.format("{0}×tamp={1}&nonce={2}", urlParams, this.timestamp, this.nonce); } public static class CertificateBuilder { /** * 公匙 */ private String token; /** * 私匙 */ private String secretKey; /** * 字符编码 */ private Charset charset = StandardCharsets.UTF_8; /** * 服务端接收到的请求参数 */ private String requestTimestamp; /** * 服务端接收到的请求参数 */ private String requestNonce; public CertificateBuilder setToken(String token) { this.token = token; return this; } public CertificateBuilder setSecretKey(String secretKey) { this.secretKey = secretKey; return this; } public CertificateBuilder setCharset(Charset charset) { this.charset = charset; return this; } public CertificateBuilder setRequestTimestamp(String requestTimestamp) { this.requestTimestamp = requestTimestamp; return this; } public CertificateBuilder setRequestNonce(String requestNonce) { this.requestNonce = requestNonce; return this; } public Certificate builder() { return new Certificate(this); } } }
2、编写拦截器
@Component public class AuthorizationInterceptor implements HandlerInterceptor { public static Logger logger = LoggerFactory.getLogger(LoggerFactory.class); @Autowired private AppTokenService appTokenService; @Autowired private SignConfig signConfig; public static final String USER_KEY = "userId"; public static final String LOGIN_TOKEN_KEY = "token"; /** * * @param request * @return */ private LinkedHashMap<String, String> getHeadersInfo(HttpServletRequest request) { LinkedHashMap<String, String> map = new LinkedHashMap<String, String> (); Enumeration headerNames = request.getHeaderNames(); while (headerNames.hasMoreElements()) { String key = (String) headerNames.nextElement(); String value = request.getHeader(key); map.put(key, value); } return map; } /** * * @param request * @return */ private LinkedHashMap<String, String> getParameterMap(HttpServletRequest request) { LinkedHashMap<String, String> map = new LinkedHashMap<String, String>(); Enumeration parameterNames = request.getParameterNames(); while (parameterNames.hasMoreElements()) { String key = (String) parameterNames.nextElement(); String value = request.getParameter(key); map.put(key, value); } return map; } /** * 功能描述 获取API接口签名实现类 * @author Hades * @date 2020/6/2 * @param token token * @param timestamp 当前时间戳 * @param nonce 随机字符串 * @return com.wise.medical.common.utils.Certificate */ private Certificate getCertificate(String token , String timestamp , String nonce) { Certificate.CertificateBuilder certificateBuilder = new Certificate.CertificateBuilder (); certificateBuilder.setToken (token); certificateBuilder.setRequestNonce (nonce); certificateBuilder.setRequestTimestamp (timestamp); certificateBuilder.setSecretKey (SystemConstant.SECRET_KEY); return new Certificate (certificateBuilder); } @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { Login annotation; if(handler instanceof HandlerMethod) { annotation = ((HandlerMethod) handler).getMethodAnnotation(Login.class); }else{ return true; } if(annotation != null){ return true; } //从header中获取token String token = request.getHeader("token"); //如果header中不存在token,则从参数中获取token if(StringUtils.isBlank(token)){ token = request.getParameter("token"); } //token为空 if(StringUtils.isBlank(token)){ throw new RRException("token不能为空"); } //查询token信息 AppTokenEntity appTokenEntity = appTokenService.queryByToken(token); if(appTokenEntity == null || appTokenEntity.getExpiretime().getTime() < System.currentTimeMillis()){ throw new RRException("登录已过期,请重新登录",-1); } //判断签名是否开启 if (signConfig.getOpen ()){ logger.info (JSONUtils.beanToJson (getParameterMap(request))); logger.info (JSONUtils.beanToJson (getHeadersInfo(request))); //获取所有请求参数 LinkedHashMap<String, String> parameterMap = getParameterMap (request); //当前请求时间戳 String timestamp = parameterMap.get ("timestamp"); if (timestamp==null){ throw new RRException ("请求超时"); } long now = System.currentTimeMillis (); //判断timestamp是否在规定时间范围内 5分钟 如超出时间范围,则拒绝 if (now - Long.parseLong (timestamp) >= DateUtils.FIVE_MINUTES){ throw new RRException ("请求超时"); } //查询携带的随机字符串nonce String nonce = parameterMap.get ("nonce"); if (nonce==null){ throw new RRException ("请求错误,请检查后再试"); } //从缓存中查找是否有相同请求,,如存在已有集合 ,则拒绝 String nonceCache = (String) ConcurrentHashMapCacheUtils.getCache(nonce); if (nonceCache != null){ throw new RRException ("请求错误,请检查后再试"); }else { // 否则,记录该nonce,并删除集合内时间戳大于5分钟的nonce,新增nonce的同时设置它的超时失效时间为5分钟 ConcurrentHashMapCacheUtils.setCache(nonce,nonce,System.currentTimeMillis()); } //获取签名 String sign = parameterMap.get ("sign"); if (sign==null){ throw new RRException ("sign为空"); } Certificate certificate = getCertificate (token , timestamp , nonce); //删除timestamp、nonce、token和sign这4个参数 parameterMap.remove ("timestamp"); parameterMap.remove ("nonce"); parameterMap.remove ("sign"); if (parameterMap.get (LOGIN_TOKEN_KEY)!=null){ parameterMap.remove ("token"); } //拼接密钥SecretKey String signStr = certificate.getSign (parameterMap); logger.info (signStr); //签名加密进行比对 if (!sign.equals (signStr)){ throw new RRException ("请求参数被篡改"); } } appTokenService.updateExpireTime(appTokenEntity); return true; } }
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。