签名规则
为了保障 请求的安全性与完整性,我们要求对接口请求参数进行签名。主要用途如下:
- 防止参数被篡改
- 验证请求来源合法性
- 保证数据完整性
参数准备规则
- 所有 非空参数值(业务参数 + 通用参数)
- 除了
sign外,不排除其它字段,例如signType仍参与 - 交易金额:单位为分,参数值不能带小数,如:132美元,则需转换为13200分
- 时间参数:所有涉及时间参数均使用精确到毫秒的13位数值,如:1622016572190
签名步骤
签名步骤
参数过滤并排序 -> 拼接字符串 -> 拼接密钥 -> 计算签名值
STEP 1:参数过滤并排序
将所有有效参数按照 参数名 ASCII 字典升序排序 (ASCII 从小到大)。
java
// Java 伪代码
Map<String, Object> validParams = params.entrySet().stream()
.filter(entry -> !"sign".equals(entry.getKey())
&& entry.getValue() != null
&& !"".equals(entry.getValue()))
.sorted(Map.Entry.comparingByKey()) // 按 key ASCII 升序
.collect(Collectors.toMap(
Map.Entry::getKey,
Map.Entry::getValue,
(oldValue, newValue) -> oldValue,
LinkedHashMap::new
));php
// PHP 伪代码
$validParams = array_filter($params, function($value, $key) {
return $key !== 'sign' && $value !== null && $value !== '';
}, ARRAY_FILTER_USE_BOTH);
ksort($validParams); // 按键名 ASCII 升序javascript
const validParams = Object.keys(params)
.filter(key => {
const value = params[key]
return key !== 'sign' && value !== undefined && value !== null && value !== ''
})
.sort()注意
签名过程中,所有参与签名的参数必须按规则排序、拼接、并确保值准确无误 sign 参数不参与签名计算
STEP 2:拼接字符串
按照键值对格式将排序后的参数值用 &key=value 拼接成字符串
注意
如果参数值是对象(包括数组需面有对象),则需要将该对象按照键名 ASCII 字典升序排序,并JSON字符串化。
java
// Java 伪代码
String queryString = validParams.entrySet().stream()
.map(entry -> {
Object value = entry.getValue();
if (value instanceof Map || value instanceof List) {
// 对象或数组先排序再转 JSON
value = JsonUtils.toSortedJson(value); // 假设有工具方法
}
return entry.getKey() + "=" + value.toString();
})
.collect(Collectors.joining("&"));php
// PHP 伪代码
$queryParts = [];
foreach ($validParams as $key => $value) {
if (is_array($value)) {
// 对象或数组先排序再 JSON 编码
$value = json_encode(sortArrayRecursively($value), JSON_UNESCAPED_UNICODE);
}
$queryParts[] = $key . '=' . $value;
}
$queryString = implode('&', $queryParts);
// sortArrayRecursively 为自定义递归排序函数,类似 javascript中 的 sortObjectjavascript
const queryString = validParams
.map(key => {
const value = params[key]
if (typeof value === 'object') {
// 对象或数组先排序再 JSON.stringify
return `${key}=${JSON.stringify(sortObject(value))}`
}
return `${key}=${value}`
})
.join('&')
// 排序对象
const sortObject = params => {
if (Array.isArray(params)) {
// 数组递归处理每个元素
return params.map(item => (typeof item === 'object' && item !== null ? sortObject(item) : item))
} else if (typeof params === 'object' && params !== null) {
// 对象按 key 排序
const sortedKeys = Object.keys(params).sort()
const result = {}
for (const key of sortedKeys) {
const value = params[key]
// 递归处理对象或数组
result[key] = typeof value === 'object' && value !== null ? sortObject(value) : value
}
return result
}
return params
}STEP 3:拼接密钥
在拼接的字符串末尾追加商户密钥(secretKey),如&key=secretKey
java
String signString = queryString + "&key=" + secretKey;php
$signString = $queryString . "&key=" . $secretKey;javascript
const signString = `${queryString}&key=${secretKey}`STEP 4:计算签名值
把待签名的字符串执行 MD5 算法计算签名值,并转成大写:
java
// Java 伪代码
String sign = DigestUtils.md5Hex(signString).toUpperCase();php
$sign = strtoupper(md5($signString));javascript
const sign = MD5(signString).toString().toUpperCase()示例
我们以下面参数为例:
json
{
"mchNo": "M1769743905",
"amount": 13200,
"tradeNo": "2021052010001",
"reqTime": 1622016572190,
"billing": {
"name": "张三",
"phone": "13800138001"
},
"items": [
{
"name": "鞋子",
"unitPrice": 12000,
"currency": "USD"
},
{
"name": "衣服",
"unitPrice": 12000,
"currency": "USD"
}
]
}商户密钥 v77ZyRbKkCEr4OPbGhiJW5ONzM2FcxvYxE2CCIm9jrvqqmQbBB7EKOOmgy6LE4Q5,则结果如下:
// => 生成的待签名字符串
amount=13200&billing={"name":"张三","phone":"13800138001"}&items=[{"currency":"USD","name":"鞋子","unitPrice":12000},{"currency":"USD","name":"衣服","unitPrice":12000}]&mchNo=M1769743905&reqTime=1622016572190&tradeNo=2021052010001&key=v77ZyRbKkCEr4OPbGhiJW5ONzM2FcxvYxE2CCIm9jrvqqmQbBB7EKOOmgy6LE4Q5
// => 签名结果
05CF9BD928B1A0413DF7B1A08C4C14A1