4. 数据结构设计:payload字段定义、过期时间、自定义声明、序列化方案
支付 SDK 的核心授权凭据是一段自包含的 ,其 payload 即为声明的载体。本章从字段规范、时效控制、自定义声明以及序列化实作四个角度,拆解 数据结构的设计要点。所有设计均基于 JWT(JSON Web ) 标准,适当扩展支付领域专属字段,确保 SDK 在异构客户端与服务端之间能安全、高效地传递授权信息。
4.1 标准载荷字段
JWT 标准(RFC 7519)定义了七个注册声明(Registered Claims),支付 SDK 必须支持其中 5 个。下表列出这些字段的作用与在支付场景中的使用方式:
| 字段名 | 全称 | 是否必须 | 支付场景说明 |
|---|---|---|---|
iss | Issuer | 是 | 颁发 的服务标识,例如 https://pay.example.com |
sub | Subject | 是 | 用户或商户的唯一ID,如 merchant_abc |
aud | Audience | 推荐 | 目标客户端标识(支付 SDK 版本/平台),辅助验签时判定 用途 |
exp | Expiration Time | 是 | 过期时间戳,用于缓存撤回与重验 |
nbf | Not Before | 可选 | 生效时间,防止预先颁发的 被提前使用 |
iat | Issued At | 是 | 签发时间,用于日志审计与刷新策略 |
jti | JWT ID | 推荐 | 唯一 标识,防止重放攻击 |
4.2 支付场景的自定义声明
支付链路需要携带商户、订单、金额等上下文。为避免与标准声明冲突,所有自定义字段均以 pay: 作为前缀命名空间。常用声明如下:
| 字段名 | 类型 | 说明 |
|---|---|---|
pay:merchant_id | string | 商户唯一编号 |
pay:order_id | string | 本次支付订单号 |
pay:amount | integer | 支付金额(最小单位,如分) |
pay:currency | string | ISO 4217 币种代码,如 CNY |
pay:payment_method | string | 支付方式标识(如 wechat_pay、alipay) |
pay:user_id | string | 支付用户标识(非明文手机号) |
pay:scope | string|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 序列化分为三步:
- 构造 JSON:将 payload 转换为 JSON 字符串(无多余空格)。
- Base64URL 编码:对 JSON 字符串进行 Base64URL 编码(去除尾部的
=并替换+/为-_),得到 payload 段。 - 拼接签名:将
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 若不通过验签,服务端必须拒绝接受。