JWT 令牌结构解析:Header、Payload、Signature 与签名算法
JSON Web Token(JWT)是 OAuth 2.0 授权流程中最常用的令牌格式。一个完整的 JWT 由三个 Base64URL 编码的部分组成,以点号(.)分隔:Header.Payload.Signature。理解这三部分的结构与签名机制,是保障支付订单安全接入的基础。
2.1 Header(头部)
Header 是 JWT 的第一段,通常包含两个字段:令牌类型(typ)和签名算法(alg)。它使用 Base64URL 编码,不加密,任何人都可以解码读取。
| 字段 | 含义 | 示例值 |
|---|---|---|
alg | 签名算法,标识使用哪种算法生成签名 | "RS256" 或 "HS256" |
typ | 令牌类型,固定为 "JWT" | "JWT" |
kid(可选) | 密钥标识,用于多密钥场景选择对应密钥 | "key-rsa-2024" |
支付安全提示: 在支付订单令牌中,Header 的
alg 字段必须显式指定,且服务端应校验该字段是否与预期算法一致,防止算法混淆攻击(Algorithm Confusion Attack)。
2.2 Payload(载荷)
Payload 是 JWT 的第二段,包含实际传递的声明(Claims)。声明分为三类:注册声明、公共声明、私有声明。在支付订单场景中,Payload 承载订单核心数据。
| 声明类型 | 常见字段 | 说明 | 支付订单示例 |
|---|---|---|---|
| 注册声明 | sub, iss, exp, iat, jti | 标准预定义字段,建议使用 | "sub":"user_1001", "exp":1735689600 |
| 公共声明 | 自定义字段,需在 IANA 注册或使用 URI 避免冲突 | 可扩展性高 | "https://pay.example.com/role":"merchant" |
| 私有声明 | 双方约定的自定义字段 | 灵活但需注意命名冲突 | "order_id":"ORD20241201", "amount":2999 |
安全红线: Payload 仅 Base64URL 编码,不是加密。绝不能在 Payload 中存放密码、信用卡号、CVV 等敏感信息。如需保护敏感数据,应使用 JWE(JSON Web Encryption)或额外加密层。
2.3 Signature(签名)
Signature 是 JWT 的第三段,用于验证令牌的完整性和真实性。签名由 Header 中指定的算法,对 base64UrlEncode(header) + "." + base64UrlEncode(payload) 进行签名计算生成。
// 签名生成伪代码
signature = sign(
base64UrlEncode(header) + "." + base64UrlEncode(payload),
secretOrPrivateKey,
{ algorithm: header.alg }
);
服务端收到 JWT 后,使用相同的密钥和算法重新计算签名,并与令牌携带的签名比对。若不一致,则拒绝请求。
2.4 RS256 与 HS256 签名算法对比
RS256(RSA Signature with SHA-256)和 HS256(HMAC with SHA-256)是 JWT 最常用的两种签名算法。它们在密钥管理、安全模型和适用场景上有本质区别。
| 对比维度 | RS256(非对称) | HS256(对称) |
|---|---|---|
| 密钥类型 | 私钥 + 公钥(密钥对) | 单一共享密钥 |
| 签名方 | 仅持有私钥的授权服务器 | 任何持有共享密钥的实体 |
| 验签方 | 任何持有公钥的客户端或服务 | 同样持有共享密钥的实体 |
| 密钥分发 | 公钥可公开,私钥严格保护 | 密钥需安全通道传输给所有验签方 |
| 性能 | 签名较慢,验签较快 | 签名和验签均快 |
| 安全风险 | 私钥泄露则整个系统受影响;公钥泄露无影响 | 共享密钥泄露,攻击者可伪造任意令牌 |
| 适用场景 | 微服务间令牌验证、第三方接入、支付订单令牌 | 单一服务内部、高吞吐低延迟场景 |
| 算法混淆防护 | 天然抗算法混淆(公钥无法用于 HMAC 验签) | 需额外校验 alg 字段,防止攻击者将 RS256 改为 HS256 |
支付订单接入建议: 在支付网关与订单服务之间,推荐使用 RS256。原因:支付场景涉及多方(商户、网关、银行),公钥可安全分发给所有验签方,私钥仅由授权服务器持有,即使某个下游服务被攻破,攻击者也无法伪造令牌。HS256 更适合内部高并发、完全信任的闭环系统。
2.5 算法混淆攻击与防护
算法混淆攻击(Algorithm Confusion Attack)是 JWT 安全中必须防范的经典攻击。攻击者将 JWT Header 中的 alg 从 RS256 改为 HS256,然后使用公钥(通常是公开的)作为 HMAC 的密钥重新签名。如果服务端未校验 alg 字段,就会使用公钥作为 HS256 的密钥进行验签,导致攻击成功。
// 攻击示例:将 RS256 令牌改为 HS256
// 原始令牌(RS256 签名)
// header: {"alg":"RS256","typ":"JWT"}
// payload: {"sub":"user","role":"admin"}
// 攻击者修改为:
// header: {"alg":"HS256","typ":"JWT"}
// 使用公钥(公开)作为 HMAC 密钥重新签名
// 服务端若未校验 alg,使用公钥验签 → 通过
防护措施:
- 服务端必须显式校验
alg字段,拒绝非预期的算法值。 - 使用 RS256 时,验签逻辑应只接受
RS256,拒绝HS256。 - JWT 库应配置白名单算法列表,例如:
jwt.verify(token, publicKey, { algorithms: ['RS256'] })。 - 避免使用
none算法,生产环境应禁用无签名令牌。
2.6 支付订单 JWT 最佳实践
- 使用 RS256:支付订单令牌涉及资金安全,非对称算法提供更强的密钥隔离。
- 设置短过期时间:
exp建议设置为 15-30 分钟,支付流程完成后立即失效。 - 包含 jti 声明:
jti(JWT ID)用于防重放攻击,每次支付请求生成唯一 ID,服务端记录已使用的 jti。 - 最小化 Payload:只存放订单 ID、用户标识、金额等必要字段,避免冗余数据。
- 签名算法白名单:服务端验签时强制指定算法,拒绝动态从 Header 读取算法。
// 支付订单 JWT 示例(Payload 部分)
{
"sub": "user_1001",
"jti": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"order_id": "ORD20241201001",
"amount": 2999,
"currency": "CNY",
"iss": "https://pay.example.com",
"iat": 1735689600,
"exp": 1735691400
}
核心要点回顾: JWT 的三段结构(Header、Payload、Signature)各自承担不同职责。Header 声明算法,Payload 承载数据,Signature 保证完整性。RS256 与 HS256 的选择直接影响支付系统的安全边界——在多方参与的支付场景中,RS256 的非对称特性是更安全的选择。算法混淆攻击是必须防范的常见威胁,通过算法白名单校验即可有效防御。