json签名需按utf-8字典序统一键序以确保跨语言一致性,前端排序顶层键并递归处理嵌套对象或扁平化params,后端须同步实现相同排序规则及编码标准。

JSON 本身不保证对象键的顺序,ECMAScript 规范明确指出:对象属性遍历顺序(如 for...in、Object.keys()、JSON.stringify())在 ES2015+ 中**对字符串键按插入顺序保持一致**,但前提是键名符合“普通标识符”规则且未被重排(如数字字符串键会按数值大小排序)。然而,在接口签名验签场景中,仅依赖语言默认行为仍不可靠——尤其涉及不同语言后端(如 Java、Go)、不同 JSON 库或历史兼容性时,键序极易错乱,导致签名不一致、验签失败。
签名前强制统一键序:按字典序序列化
最稳妥的做法是:签名计算前,将待签名对象的所有键按 UTF-8 字典序(而非插入序)排序,再序列化。这能确保前后端无论使用何种 JSON 库,只要遵循同一排序规则,结果就完全一致。
- 前端 JavaScript 示例(使用
JSON.stringify配合排序后的 key 数组):
const payload = {
timestamp: 1717023456,
nonce: "abc123",
method: "user.get",
params: { id: 1001, status: "active" }
};
const sortedKeys = Object.keys(payload).sort();
const orderedPayload = {};
for (const key of sortedKeys) {
orderedPayload[key] = payload[key];
}
const signString = JSON.stringify(orderedPayload);
// → {"method":"user.get","nonce":"abc123","params":{"id":1001,"status":"active"},"timestamp":1717023456}
// ✅ 所有键严格字典序
避免嵌套对象干扰:签名字段扁平化或约定结构
深层嵌套对象的键序难以统一控制(如 params.id 和 params.status 的顺序依赖 params 自身序列化逻辑)。推荐两种实践:
-
方案一(推荐):签名只包含顶层字段,且
params作为 JSON 字符串参与签名(即先序列化 params,再拼入签名原文)。这样 params 内部顺序由前端单方决定,后端解析后直接比对字符串,无需重复排序。 - 方案二:约定 params 必须为字典序排序后的 JSON 字符串,由前端生成并透传。后端收到后原样用于签名计算,不重新解析再序列化。
后端必须同步实现相同排序逻辑
前端按字典序排序,后端也必须用完全相同的规则处理:
- Java(Jackson):禁用默认的“按声明顺序”,改用
@JsonPropertyOrder(alphabetic = true)或手动构建TreeMap; - Go(encoding/json):标准库默认无序,需用
map[string]interface{}配合sort.Strings+ 自定义序列化; - Python(json.dumps):传入
sort_keys=True即可。
关键点:前后端使用的“字典序”必须基于相同编码(通常是 UTF-8 字节序),避免 Unicode 归一化差异。
额外建议:签名原文排除无关字段,加盐防篡改
签名计算前应明确字段白名单(如只含 method、timestamp、nonce、params),过滤掉 sign、signature 等签名自身字段,防止循环依赖。同时可加入固定 salt(如 API 密钥)参与哈希,提升安全性:
const signContent = JSON.stringify(orderedPayload) + "&salt=your_api_secret";
const signature = CryptoJS.HmacSHA256(signContent, apiKey).toString();
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











