1.业务背景:
一些加积分,兑换奖品等接口,为了确保数据参数在传输过程中未经过篡改,都需要对接口数据进行加签,然后在接口服务器端对接口参数进行验签,确保两个签名是一样的,验签通过之后再进行业务逻辑处理。
2.实现思路:
双方约定好,参数按特定顺序排列,比如按首字母的顺序排列,如url:http://xxx/xxx.do?a=wersd&b=sd2354&c=4&signature=XXXXXXXXXXXX(signature为传入的签名),等你拿到入参后,将参数串a=wersd&b=sd2354&c=4按你们约定的签名规则,自己用md5加签一次,然后和入参的signature值对比,以确认调用者是否合法,这就是接口签名验证的思路。
3.签名规则:
sign=MD5(“参数1的键”+参数1的值+…+“参数N的键”+“参数N的值”+appid)
例如:url:http://127.0.0.1:5443/axfund-alipay/anxinGuess/getRanking?userId=wang&appid=165413218165&signature=sdafd45a1sda2d1a21ds3a
加密后的签名:
sign=MD5(userIdwang165413218165)
4.代码实现
目前主要是以java 中的拦截器实现(创建一个拦截器)
package cn.com.dollar.axfund.interceptor;
import cn.com.dollar.axfund.anxinguess.resp.SignResponseVo;
import com.alibaba.fastjson.JSON;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
/**
* @author wanglulei
* @create 2019-03-27 16:47
* @desc 验签拦截器
**/
@Slf4j
public class SignatureInInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object o) throws Exception {
//获取验签
String signature = request.getParameter("signature");
String resStr = JSON.toJSONString(new SignResponseVo(SignResponseVo.Code.SIGNATURE_FAIL, null));
response.setCharacterEncoding("UTF-8");
response.setContentType("application/json; charset=utf-8");
PrintWriter out = null;
if (StringUtils.isBlank(signature)) {
log.info("签名字段为空");
try {
out = response.getWriter();
out.append(resStr);
log.info(resStr);
} catch (IOException e) {
log.error("验签报错", e);
} finally {
if (out != null) {
out.close();
}
}
}
//校验
Boolean right = SignUtil.checkSign(request);
if (right) {
return true;
}
try {
out = response.getWriter();
out.append(resStr);
log.info(resStr);
} finally {
if (out != null) {
out.close();
}
}
return false;
// return true;
}
@Override
public void postHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, ModelAndView modelAndView) throws Exception {
// 在处理过程中,执行拦截
}
@Override
public void afterCompletion(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) throws Exception {
// 执行完毕,返回前拦截
}
}
加密工具类
package cn.com.dollar.axfund.interceptor;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.security.GeneralSecurityException;
import java.security.MessageDigest;
import java.util.Date;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
import javax.servlet.http.HttpServletRequest;
import com.alibaba.fastjson.JSONObject;
import org.apache.commons.io.IOUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class SignUtil {
private static Logger LOGGER = LoggerFactory.getLogger(SignUtil.class);
private static final String secretKeyOfWxh = "YgAqeZq1eM#6#xTWkjtEGO%Ol4oxzBIlYI#k75HJml4bCr!F8YTqySDueKRY%1GB";
public static void main(String[] args) {
//参数签名算法测试例子
HashMap<String, String> signMap = new HashMap<String, String>();
signMap.put("devid", "BC5549D899ED");
signMap.put("userId", "1");
signMap.put("type", "worker");
signMap.put("name", "中文测试");
System.out.println("得到签名sign1:" + getSign(signMap, secretKeyOfWxh));
}
/**
* 唯修汇外部接口签名验证
*
* @param request
* @return
*/
public static Boolean checkSign(HttpServletRequest request) throws IOException {
Boolean flag = false;
//签名
String sign = request.getParameter("signature");
Enumeration<?> pNames = request.getParameterNames();
InputStream is= null;
is = request.getInputStream();
String bodyInfo = IOUtils.toString(is, "utf-8");
Map<String, String> params = new HashMap<String, String>();
JSONObject jsonObject = JSONObject.parseObject(bodyInfo);
// jsonObject.forEach();
while (pNames.hasMoreElements()) {
String pName = (String) pNames.nextElement();
if ("signature".equals(pName)) continue;
String pValue = (String) request.getParameter(pName);
params.put(pName, pValue);
}
if (sign.equals(getSign(params, secretKeyOfWxh))) {
flag = true;
}
return flag;
}
public static String utf8Encoding(String value, String sourceCharsetName) {
try {
return new String(value.getBytes(sourceCharsetName), "UTF-8");
} catch (UnsupportedEncodingException e) {
throw new IllegalArgumentException(e);
}
}
private static byte[] getMD5Digest(String data) throws IOException {
byte[] bytes = null;
try {
MessageDigest md = MessageDigest.getInstance("MD5");
bytes = md.digest(data.getBytes("UTF-8"));
} catch (GeneralSecurityException gse) {
throw new IOException(gse);
}
return bytes;
}
private static String byte2hex(byte[] bytes) {
StringBuilder sign = new StringBuilder();
for (int i = 0; i < bytes.length; i++) {
String hex = Integer.toHexString(bytes[i] & 0xFF);
if (hex.length() == 1) {
sign.append("0");
}
sign.append(hex.toUpperCase());
//sign.append(hex.toLowerCase());
}
return sign.toString();
}
/**
* 得到签名
*
* @param params 参数集合不含secretkey
* @param secret 验证接口的secretkey
* @return
*/
public static String getSign(Map<String, String> params, String secret) {
String sign = "";
StringBuilder sb = new StringBuilder();
//step1:先对请求参数排序
Set<String> keyset = params.keySet();
TreeSet<String> sortSet = new TreeSet<String>();
sortSet.addAll(keyset);
Iterator<String> it = sortSet.iterator();
//step2:把参数的key value链接起来 secretkey放在最后面,得到要加密的字符串
while (it.hasNext()) {
String key = it.next();
String value = params.get(key);
sb.append(key).append(value);
}
sb.append(secret);
byte[] md5Digest;
try {
//得到Md5加密得到sign
md5Digest = getMD5Digest(sb.toString());
sign = byte2hex(md5Digest);
} catch (IOException e) {
LOGGER.error("生成签名错误", e);
}
return sign;
}
}
配置拦截器,使其生效(mvc-core-config.xml)
<!-- 配置拦截器 -->
<mvc:interceptors>
<!-- 配置验签拦截器 -->
<mvc:interceptor>
<mvc:mapping path="/**"/>
<bean class="cn.com.dollar.axfund.interceptor.SignatureInInterceptor"></bean>
</mvc:interceptor>
</mvc:interceptors>