【需求】
最近在公司做的微信支付和给其他系统提供接口中,都用到了签名算法,顾名思义,在接口的调用中,我们可以通过对接收的参数生成签名,从而判断传的签名与接口中通过算法得到的签名是否一致来保证数据的安全性。
【简述】
这两次接触都是通过MD5算法实现的,通用步骤一般都是:
第一步:设所有发送或者接收到的数据为集合M,将集合M内非空参数值的参数按照参数名ASCII码从小到大排序(字典序),使用URL键值对的格式(即key1=value1&key2=value2…)拼接成字符串stringA。
第二步:在stringA最后拼接上key得到stringSignTemp字符串,并对stringSignTemp进行MD5运算,再将得到的字符串所有字符转换为大写,得到sign值signValue
而对于加密的规则,则是我们可以自定义的。但其中也包括一些通用规则,如下:
1)参数名ASCII码从小到大排序(字典序);
2)如果参数的值为空,则不参与签名;
3)参数名区分大小写;
4)验证调用返回或接口主动通知签名时,传送的参数不参与签名,将生成的签名和该sign值作校验。
【举例】
假设传递的参数如下:
appid: wxd930ea5d5a258f4f
mch_id: 10000100
device_info: 1000
body: test
nonce_str: ibuaiVcKdpRxkhJA
第一步:对参数按照key=value的格式,并按照参数名ASCII字典序排序如下:
stringA=”appid=wxd930ea5d5a258f4f&body=test&device_info=1000&mch_id=10000100&nonce_str=ibuaiVcKdpRxkhJA”;
第二步:拼接API密钥:
stringSignTemp=stringA+”&key=192006250b4c09247ec02edce69f6a2d” //注:key为商户平台设置的密钥key
sign=MD5(stringSignTemp).toUpperCase()=”9A0A8659F005D6984697E2CA0A9CF3B7” //注:MD5签名方式
【代码实现】
public class SignatureUtil {
//生成签名
public static String createSign(String characterEncoding, SortedMap<String, Object> parameters, String appSecret) {
StringBuffer sb = new StringBuffer();
sb.append(appSecret);
Set es = parameters.entrySet();
Iterator it = es.iterator();
while (it.hasNext()) {
Map.Entry entry = (Map.Entry) it.next();
String k = (String) entry.getKey();
Object v = entry.getValue();
if (null != v && !"".equals(v) && !"sign".equals(k)) {
sb.append(k + v);
}
}
sb.append(appSecret);
System.out.println(sb.toString());
String sign = MD5Util.MD5Encode(sb.toString(), characterEncoding).toUpperCase();
return sign;
}
}
自定义拦截器,实现preHandle(),除不需要拦截的请求外,都需要先进行签名校验:
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
String requestUrl = request.getRequestURI();
logger.info(requestUrl);
if (uncheckUrls.contains(requestUrl)) {
return true;
} else {
//1. 获取request中所有参数集合
Enumeration<String> enu = request.getParameterNames();
//2. 将request中的参数集合排序
SortedMap<String, Object> parameterMap = new TreeMap<String, Object>();
//3. 构造参数集合
while (enu.hasMoreElements()) {
String paraName = enu.nextElement();
parameterMap.put(paraName, request.getParameter(paraName));
}
logger.info(parameterMap);
if (parameterMap != null && !parameterMap.isEmpty()) {
//4. 获取请求参数中的appKey和appSecret
String appKey = request.getParameter("appKey");
String appSecret = request.getParameter("appSecret");
if (StringUtils.isBlank(appKey) || StringUtils.isBlank(appSecret)) {
logger.error("appKey=" + appKey + "; appSecret=" + appSecret);
logger.error("获取app_key或appSecret失败,验证签名失败!");
return false;
}
//5. 获取请求参数中的sign
String sign = request.getParameter("sign");
if (StringUtils.isBlank(sign)) {
logger.error("sign=" + sign);
logger.error("获取签名失败,验证签名失败!");
return false;
}
//6. 将参数集合生成签名
String signResult = "";
//生成签名
signResult = SignatureUtil.createSign("UTF-8", parameterMap, appSecret);
logger.info("signResult :" + signResult);
//7. 验证签名是否一致
if (!StringUtils.equals(sign, signResult)) {
logger.error("签名不一致,验证签名失败!");
return false;
}
}
}
return true;
}