4. 数据结构设计:payload字段定义、过期时间、自定义声明、序列化方案

支付 SDK 的核心授权凭据是一段自包含的 ,其 payload 即为声明的载体。本章从字段规范、时效控制、自定义声明以及序列化实作四个角度,拆解 数据结构的设计要点。所有设计均基于 JWT(JSON Web ) 标准,适当扩展支付领域专属字段,确保 SDK 在异构客户端与服务端之间能安全、高效地传递授权信息。

4.1 标准载荷字段

JWT 标准(RFC 7519)定义了七个注册声明(Registered Claims),支付 SDK 必须支持其中 5 个。下表列出这些字段的作用与在支付场景中的使用方式:

字段名全称是否必须支付场景说明
issIssuer是颁发 的服务标识,例如 https://pay.example.com
subSubject是用户或商户的唯一ID,如 merchant_abc
audAudience推荐目标客户端标识(支付 SDK 版本/平台),辅助验签时判定 用途
expExpiration Time是 过期时间戳,用于缓存撤回与重验
nbfNot Before可选 生效时间,防止预先颁发的 被提前使用
iatIssued At是 签发时间,用于日志审计与刷新策略
jtiJWT ID推荐唯一 标识,防止重放攻击

4.2 支付场景的自定义声明

支付链路需要携带商户、订单、金额等上下文。为避免与标准声明冲突,所有自定义字段均以 pay: 作为前缀命名空间。常用声明如下:

字段名类型说明
pay:merchant_idstring商户唯一编号
pay:order_idstring本次支付订单号
pay:amountinteger支付金额(最小单位,如分)
pay:currencystringISO 4217 币种代码,如 CNY
pay:payment_methodstring支付方式标识(如 wechat_pay、alipay)
pay:user_idstring支付用户标识(非明文手机号)
pay:scopestring|array授权范围,例如 payment:write
设计建议:自定义声明不宜超过 10 个,单个 payload 大小控制在 1 KB 以内。使用 pay: 前缀可避免与外部系统或未来 JWT 标准扩展冲突。

4.3 过期时间设计

过期时间直接决定 的生命周期与安全边界:

  • 访问令牌(Access ):通常设为 15 分钟(900 秒)。支付 SDK 持有该令牌发起支付请求,短时效降低泄露风险。
  • 刷新令牌(Refresh ):允许更长窗口,例如 7 天(604800 秒)。刷新令牌不与支付请求直接耦合,且必须在受信客户端传递。
  • NBF 偏移:设置 nbf = iat 或增加 5 秒时钟偏移,避免分布式时间不同步导致的验签失败。
  • 时钟容差:服务端验签时允许 60 秒的偏差,宽容服务端与客户端时钟误差。
常见错误:将 exp 设置为固定日期(如每年 12 月 31 日),导致 永久有效。必须使用时间戳,且动态计算。

4.4 序列化方案

JWT 序列化分为三步:

  1. 构造 JSON:将 payload 转换为 JSON 字符串(无多余空格)。
  2. Base64URL 编码:对 JSON 字符串进行 Base64URL 编码(去除尾部的 = 并替换 +/ 为 - _),得到 payload 段。
  3. 拼接签名:将 header 段与 payload 段用 . 连接,使用 HMAC 或 RSA 算法计算签名,再 Base64URL 编码得到 signature 段。

以下是一个支付 的完整 payload JSON 结构示例:

{
  "iss": "https://pay.example.com",
  "sub": "merchant_abc",
  "aud": ["pay_sdk_android", "pay_sdk_ios"],
  "exp": 1735689600,
  "iat": 1735603200,
  "jti": "d5f6e7a8-b9c0-4d1e-8f2a-3b4c5d6e7f80",
  "pay:merchant_id": "merchant_abc",
  "pay:order_id": "order_20240101_001",
  "pay:amount": 1999,
  "pay:currency": "CNY",
  "pay:payment_method": "wechat_pay",
  "pay:user_id": "user_789012",
  "pay:scope": "payment:write"
}

4.5 方案对比

除 JWT 外,支付 SDK 也曾考虑过其他序列化格式。下表对比三种主流方案:

方案体积可读性签名支持推荐场景
JWT (Base64URL + JSON)中等(约 200–500 B)可解码查看(需 Base64解码)原生标准支付授权
原始 JSON(无编码)最小最佳需外部签名内部微服务间短链路
CBOR (RFC 7049)最小(二进制)差需自定义物联网 / 极低带宽环境
选型结论:支付 SDK 默认使用 JWT 序列化,因为其有标准签名机制,且 Base64URL 可安全地在 HTTP Header 或 URL 中传递。仅在 SDK 嵌入极端受限设备(如 POS 机)时,才考虑 CBOR。

4.6 实现要点与常见错误

  • 敏感信息:payload 仅 Base64 编码而非加密,严禁存放明文密码、卡号、CVV。
  • 字段类型一致性:金额、时间戳必须使用数字类型(64 位整数),避免客户端与服务端解析行为不一致。
  • 时钟同步:所有时间戳均使用 UTC #epoch(无时区),服务端验签时统一按 UTC 判定。
  • JTI 生成:使用 UUIDv4 或带时间戳的随机数,确保全局唯一。
  • 签名验证不忽略 payload:伪造的 payload 若不通过验签,服务端必须拒绝接受。
JWT 数据结构与序列化流程 Header {"alg":"HS256", "typ":"JWT"} → Base64URL 编码 Payload {"iss":"...", "exp":1735689600, "pay:amount":1999, ...} → Base64URL 编码 Signature HMAC(secret) 或 RSA 签名 编码后 编码后 序列化 header.payload.signature eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9 . eyJpc3MiOiJodHRwczovL3BheS5leGFtcGxlLmNvbSIsInN1YiI6... . s9f3h2d8f7a0c4b1e5d6... (示例截断) 关键设计约束 ① Payload 大小 ≤ 1KB ② 所有数字字段使用 64 位整数 ③ 自定义声明以 pay: 前缀避免冲突 ④ 敏感数据必须加密存储,不可直接放入 payload ⑤ 签名不覆盖 payload 则无效