说明
一、签名规则
所有请求签名的参数,均需依照“参数名=参数值”的格式,按首字符字典顺序(ascii 值大小)进行排序,如遇相同首字符,则判断第二个字符,以此类推。
所有待签名字符串据需根据“参数名 1=参数值 1&参数名 2=参数值 2&….&参数名 N=参数值 N”的规则进行拼接(如果参数值为数组,请转为json格式)。
在对请求的参数做签名时,这些参数必须来源于请求参数列表,并且除去列表中的参数 sign。
在对请求的参数做签名时,需过滤掉为空或false的参数,true值转化文文本格式“1”。
拿到请求时的待签名字符串后,将私钥(直接拼接到待签名字符串之后,形成新的字符串。
利用 MD5 的签名函数对新的字符串进行签名运算,得到 32 位签名结果字符串(该字符串赋值于参数 sign)。
二、签名的生成
sign 字段生成规则如下:
2、数据排序
按照数据第一个字符的键值 ASCII 码递增排序(字母升序排序)。
3、数据拼接
将排序后的数据名与数据值,组合成“参数名=参数值”的格式,并把这些参数用 & 字符连接起来,此时生成的字符串为最初的待签名字符串。
示例:
{"orderSource":"1","goodsList":[{"goodsQty":"1","skuId":"1400633276659449858"}],"buyerName":1,"shipArea":"广东省,深圳市,龙岗区","shipAreaCode":"440000,440300,440307","shipName":"test","shipAddress":"测试地址","shipMobile":"13943256432",
"timeStamp":"1638424611703"}经过 “参数筛选”,“参数排序”,“参数拼接” 得到如下字符串:
buyerName=1&goodsList=[{"goodsQty":"1","skuId":"1400633276659449858"}]&orderSource=1&shipAddress=测试地址&shipArea=广东省,深圳市,龙岗区&shipAreaCode=440000,440300,440307&shipMobile=13943256432&shipName=test&timeStamp=1638424611703
注意:传参数据为空的参数不参与拼接验签。
然后拼接 appSecret(签名秘钥,测试环境为12345,生产环境统一约定更换) 后的最终的待签名字符串:
buyerName=1&goodsList=[{"goodsQty":"1","skuId":"1400633276659449858"}]&orderSource=1&shipAddress=测试地址&shipArea=广东省,深圳市,龙岗区&shipAreaCode=440000,440300,440307&shipMobile=13943256432&shipName=test&timeStamp=1638424611703&123456
4、生成签名
使用 md5Hex()方法对最终的待签名字符串进行签名:
md5Hex('buyerName=1&goodsList=[{"goodsQty":"1","skuId":"1400633276659449858"}]&orderSource=1&shipAddress=测试地址&shipArea=广东省,深圳市,龙岗区&shipAreaCode=440000,440300,440307&shipMobile=13943256432&shipName=test&timeStamp=1638424611703&123456')
md5Hex:已计算结果为准
请求参数的签名结果(即 sign 参数的值)为:已计算结果为准
三.签名算法
签名核心工具类方法
/**
* 获取签名
*
* @param params
* @param appSecret
* @return
*/
public static String getSign(Map<String, String> params, String appSecret) {
for (Map.Entry<String, String> entry : params.entrySet()) {
// 拼接参数值字符串并进行utf-8解码,防止中文乱码产生
String value = "";
try {
value = URLDecoder.decode(entry.getValue(), StandardCharsets.UTF_8);
} catch (Exception e) {
log.error(e.getMessage());
}
if (StringUtils.isBlank(value)) {
continue;
}
params.put(entry.getKey(), value);
}
// 将参数以参数名的字典升序排序
Map<String, String> sortParams = new TreeMap<>(params);
Set<Map.Entry<String, String>> entrys = sortParams.entrySet();
// 遍历排序的字典,并拼接格式
StringBuilder valueSb = new StringBuilder();
for (Map.Entry<String, String> entry : entrys) {
if (StringUtils.isNotBlank(entry.getValue())) {
valueSb.append(entry.getKey()).append("=").append(entry.getValue()).append("&");
}
}
valueSb.append(appSecret);
String sign = valueSb.toString();
log.info("#####getSign={}",sign);
try {
sign = DigestUtils.md5Hex(sign.getBytes(StandardCharsets.UTF_8)); //NOSONAR
} catch (Exception e) {
log.error(e.getMessage());
}
return sign;
}
四.签名验证
public static void main(String[] args) {
String bodyStr = "{\"skuId\":42}";
Map<String, String> mapType = JSON.parseObject(bodyStr, new TypeReference<>() {
});
mapType.put(OpzUtils.TIME_STAMP, "1545804554075");
String chkSign = getSign(mapType, "270c449611614f4f92a8b36433793fdc");
System.out.println("chkSign ======== " + chkSign + ", sign ====== e2bd3279cfe9c74623a8be6fa138231f");
}
执行结果:
#####getSign=appKey=1395984999469318145&randomNumber=465654555656544&skuId=42&timeStamp=1545804554075&version=1.0&270c449611614f4f92a8b36433793fdc
chkSign ======== e2bd3279cfe9c74623a8be6fa138231f, sign ====== e2bd3279cfe9c74623a8be6fa138231f
接口地址
开发环境:
生产环境:
加密密钥
开发环境:123456
生产环境:上线前确定