Java开发中,经常会有与第三方接口对接的情况。为了提高对接中数据传输的安全性,防止请求参数被篡改,通常都需要增加签名sign。比如支付接口,依照双方事先约定好的规则,请求方对数据进行加签,接口提供方则对签名进行验证,验证通过才能放行,进行后续的业务逻辑处理。
这里最重要的就是签名规则,将参数按照一定顺序排列,添加密钥,然后加密,得到sign。
1、对请求参数加密。此处是以get请求链接为例,以hashmap默认的排序,前后加上密钥,以MD5的形式,进行加密,获得sign,然后拼接在url之后。
/**
* 跳转链接地址加密
* @param taskUrl
* @return
* @throws Exception
*/
public String dingAuth(String taskUrl) throws Exception {
if (Assert.isEmpty(secret)) {
throw new BusinessException("密钥为空");
}
URL url = new URL(taskUrl);
String query = url.getQuery();
//此处处理很繁琐,主要是保持参数顺序
String[] arrs = query.split("&");
Map<String, Object> map = new HashMap<>();
for(String arr : arrs){
map.put(arr.substring(0, arr.indexOf("=")), arr.substring(arr.indexOf("=")+1));
}
StringBuilder sb = new StringBuilder(secret);
map.forEach((k, v) -> {
//为空的不参与加密
if(!StringUtils.isEmpty(v)){
sb.append(k).append("=").append(v).append("&");
}
});
sb.delete(sb.length()-1, sb.length());
sb.append(secret);
//MD5加密
String encryValue = DigestUtils.md5DigestAsHex(sb.toString().getBytes());
taskUrl = taskUrl + "&sign=" + encryValue;
return taskUrl;
}
2、对请求进行验签。其实验签和加签的原理相同。请求地址中剥离出sign,然后将剩余的参数按照加签同样的规则进行加密,加密结果与之前的sign作比较,如果一致则验证通过。
/**
* 请求签名验证
* @param queryParams
*/
public void auth(Map<String, Object> queryParams) {
String sign = (String) queryParams.get("sign");
if (StringUtils.isEmpty(sign)) {
throw new BusinessException("非法参数");
}
if (Assert.isEmpty(secret)) {
throw new BusinessException("密钥为空");
}
// sign不参与签名
queryParams.remove("sign");
Map<String, Object> map = new TreeMap<String, Object>(queryParams);
StringBuilder strBuilder = new StringBuilder(secret);
map.forEach((k, v) -> {
if(v != null){
strBuilder.append(k).append("=").append(v).append("&");
}
});
strBuilder.delete(strBuilder.length()-1, strBuilder.length());
strBuilder.append(secret);
//MD5加密
String encryValue = DigestUtils.md5DigestAsHex(strBuilder.toString().getBytes());
if (!sign.equals(encryValue)) {
throw new BusinessException("签名错误");
}
}
值得一提的是,签名机制仅能确保请求中的参数不被篡改,并不能保证传输中敏感数据的安全性。