JSON 的字段顺序变了,为什么签名就对不上了?
规范说对象是无序的,但签名是对字节算的——顺序一变,字节就变。
问题的根源在于两个层面被混在一起了。语义层面上,对象就是一个键值映射,先写哪个键不影响它表达的内容。字节层面上,同样的映射可以序列化成很多种不同的文本——键的顺序不同、有没有缩进、冒号后面有没有空格,都会生成不一样的字符串。签名算的是后者。
规范本身也承认这个差异:它明确说不同解析库「是否让对象的成员顺序对调用方可见」做法不一,并指出只有行为不依赖成员顺序的实现才具备互操作性。既然顺序在不同实现里可见性都可能不同,那么依赖顺序的签名方案就必须自己定义一套规范化的序列化方式,不能指望「大家都按自然顺序输出」。
这类故障通常发生在对接环节:本地测试一切正常,对方接进去就报签名校验失败。排查半天发现,两边把同一份数据序列化成了键顺序不同的两串文本,而签名只认其中一串。
理解这件事只需要区分两个词:语义相等和字节相等。前者指解析之后的数据结构一样,后者指参与运算的字符完全一样。签名算的是后者,而人眼判断的是前者,这就是误差的来源。
1规范怎么说的
原文其实已经给了提示。
| 层面 | 规范的态度 | 对实现的影响 |
|---|---|---|
| 对象 | 定义为「无序」的名称与值对集合 | 语义上顺序无意义,解析结果应当一致 |
| 数组 | 定义为「有序」序列 | 顺序是数据的一部分,不能重排 |
| 空白 | 结构符号前后的空白属于无关紧要 | 缩进和换行不改变语义,但改变字节 |
| 成员顺序的可见性 | 不同实现做法不一,规范明确承认这一点 | 行为不依赖顺序的实现才可互操作 |
把这句话记住就够了:规范化不是为了好看,而是为了让「语义相同的两份数据」在字节层面也完全相同。签名方案要么约定规范化算法,要么直接对原始字节签名并要求原样传递。
2为什么签名对字节敏感
几种常见场景都栽在同一个点上。
- 令牌类签名
- 签发方是对编码后的原始载荷字节做签名,规范明确说编码前不做规范化。所以载荷里键序或空白变一个字节,验签立刻失败。
- 回调通知的验签
- 很多服务把请求体原文参与签名计算。中间只要有一层代理重新序列化过 JSON,字节就变了。
- 内容寻址与缓存键
- 用摘要做标识时,同一份数据的不同序列化形式会得到不同的标识,缓存命中率骤降。
- 幂等与去重
- 用摘要判重时,同样的内容因序列化差异被当成两次不同请求,去重逻辑失效。
一个实用做法:校验和计算之前,先按确定的规则重排键并固定紧凑格式,再做摘要。这样无论上游怎么序列化,计算结果都一致。
3重复键与精度这两处也常出事
它们和顺序问题同源,都来自「规范允许、行为不定」。
- 重复键是合法的
- 规范并不禁止同名成员出现两次,但明确说遇到重复名时软件行为不可预测:有的取最后一个,有的取第一个,有的收集全部,有的直接报错。
- 所以重复键必须当 bug 处理
- 不能依赖任何一种解析结果。生成数据时要保证键唯一;收到可疑数据时先做检查,而不是默默接受。
- 数字精度有安全区间
- 整数只在某个绝对值范围内才能被通用实现精确表示,超出后不同语言可能给出不同结果。大整数建议用字符串传递。
- 注释和尾逗号不是标准 JSON
- 带注释、尾逗号的写法属于扩展方言,严格解析器会直接拒绝。需要写说明就加一个约定的说明字段。
4对接时怎么少踩坑
几条可以直接落地的约定。
- 明确约定规范化方式
- 要么双方都按同一套规则重排与格式化,要么约定「签名对原始字节进行、传输必须原样转发」。
- 验签不要先解析再重新序列化
- 很多事故就是这一步引入的:解析之后重新输出,键序和空白都变了。要用原始字节参与运算。
- 统一编码
- 跨系统交换必须用同一种编码,且不要带字节序标记,否则前几个字节的差异就足以让摘要不同。
- 把幂等键和签名分开
- 用稳定的业务键做幂等判断,而不是用整份报文的摘要,能避免大量因格式差异产生的误判。
顺着这个问题往下用
全部工具总览 →相关疑问
既然对象是无序的,为什么解析库还能记住顺序?
因为很多实现保留了插入顺序,这在日常使用上更方便,规范也承认这种可见性差异存在。但「能记住」不等于「应该依赖」——换一个语言、换一个库,或者中间经过一次转发,顺序就可能变。所以规范建议的行为是不依赖顺序。
我在本地测得好好的,为什么对方验签失败?
最常见的原因是两边序列化方式不同:键顺序不同、紧凑与美化不同、编码不同。还有一种情况是中间有代理或框架重新序列化过请求体。让对方把收到的原始字节打印出来对比,通常一眼就能看出差异在哪。
能不能干脆规定所有键按字典序输出?
可以,这是很常见的约定,但必须双方都遵守,而且要固定其他格式细节(分隔符、是否转义非 ASCII 字符、数字怎么表示)。只约定排序而不约定这些,还是会在别的地方出现字节差异。
重复键为什么危险到要当 bug?
因为不同实现的处理方式完全不同,同一份数据在不同环节可能被理解成不同的值。在一个系统里取到了最后一个值,在另一个系统里取到了第一个值,数据就静默地分叉了——这类问题往往在很久之后才暴露,且极难排查。
继续看:相关问题与资料
全部问题解答 →内容说明:本页针对具体使用问题作答,方法步骤与数值由本站自行整理与复算,事实性内容以对应官方规范与文档为准。内容最后核对日期:2026-10-09。若发现错误,欢迎通过页脚反馈入口告知。
速查在线工具箱