4、JWT Payload 详解:注册声明、公共声明与私有声明
JWT 的 Payload(载荷)是承载实际数据的核心部分,它是一个 JSON 对象,包含一组声明(Claims)。声明是关于实体(通常是用户)和附加数据的陈述。根据 JWT 标准(RFC 7519),声明分为三类:注册声明、公共声明和私有声明。理解这三类声明的用途、命名规则和校验逻辑,是构建安全 JWT 契约的基础。
核心原则: Payload 仅进行 Base64Url 编码,并未加密。任何持有 Token 的人都可以解码读取其内容。因此,严禁在 Payload 中存放密码、密钥、身份证号等敏感信息。
4.1 注册声明(Registered Claims)
注册声明是 JWT 标准预定义的一组非强制性但推荐的声明。它们提供了互操作性,确保不同系统之间能够理解 Token 的基本语义。以下是七个核心注册声明:
| 声明名 | 全称 | 类型 | 说明与校验要点 |
|---|---|---|---|
| iss | Issuer | String | 签发者。标识签发 Token 的实体(如 https://auth.example.com)。接收方应校验 iss 是否与预期签发者匹配。 |
| sub | Subject | String | 主题。标识 Token 所代表的主体(通常是用户 ID)。在同一个 Issuer 下,sub 应全局唯一。 |
| aud | Audience | String / Array | 受众。标识 Token 的目标接收方。接收方必须校验 aud 是否包含自身标识,防止 Token 被滥用。 |
| exp | Expiration Time | NumericDate | 过期时间。Token 在此时间之后失效。必须校验当前时间是否早于 exp。 |
| nbf | Not Before | NumericDate | 生效时间。Token 在此时间之前不可用。建议校验当前时间是否晚于 nbf。 |
| iat | Issued At | NumericDate | 签发时间。标识 Token 的生成时间。可用于判断 Token 的“年龄”或进行日志审计。 |
| jti | JWT ID | String | 唯一标识符。用于防止 Token 重放攻击。服务端需记录已使用的 jti,直到 Token 过期。 |
时间校验最佳实践: 校验
exp 和 nbf 时,建议设置一个“时钟偏差”(Leeway),通常为 30~60 秒,以容忍客户端与服务端之间的微小时间差异。
注册声明校验流程图
下图展示了服务端在接收到 JWT 后,对注册声明进行校验的典型流程:
4.2 公共声明(Public Claims)
公共声明是由 JWT 使用者自行定义,但为了避免冲突,需要在 IANA JSON Web Token Claims Registry 中注册,或使用 Collision-Resistant Name(防冲突命名)的声明。公共声明的目的是标准化一些通用场景下的数据字段,促进跨系统协作。
- 注册方式: 向 IANA 提交注册申请,获取一个唯一的声明名称。
- 防冲突命名: 使用 URI 作为声明名(例如
https://example.com/claims/role),确保全球唯一性。 - 常见示例:
name、email、role、picture等,但这些字段若未在 IANA 注册,严格来说应视为私有声明。
注意: 公共声明虽然名称是“公共”的,但并不意味着其内容是公开的。它仅指声明名称的命名空间是公开且标准化的。内容的安全性仍取决于传输和存储方式。
4.3 私有声明(Private Claims)
私有声明是通信双方(签发者和接收者)之间私下约定的声明,用于交换非标准化的数据。私有声明不要求在 IANA 注册,也不强制使用防冲突命名。双方只需在契约中约定好声明名称和数据类型即可。
- 使用场景: 传递用户角色、权限列表、会话 ID、设备信息等业务相关数据。
- 命名约定: 为避免与注册声明或未来的公共声明冲突,建议使用较长的、具有业务含义的名称(如
user_role、permissions)。 - 风险提示: 由于没有全局命名空间约束,不同系统间的私有声明名称可能意外冲突。在微服务架构中,建议通过 API 网关或契约文档统一管理。
4.4 三类声明对比
| 特性 | 注册声明 | 公共声明 | 私有声明 |
|---|---|---|---|
| 标准化程度 | 高(RFC 7519 定义) | 中(IANA 注册或 URI 命名) | 低(双方约定) |
| 命名冲突风险 | 无 | 低 | 高 |
| 互操作性 | 强 | 较强 | 弱(仅限约定双方) |
| 使用场景 | Token 生命周期控制 | 通用业务数据 | 特定业务数据 |
| 是否必须校验 | exp/iss/aud 强烈建议 | 按需校验 | 按需校验 |
4.5 Payload 结构示例
以下是一个包含三类声明的完整 Payload 示例:
{
// 注册声明
"iss": "https://auth.example.com",
"sub": "user_1234567890",
"aud": ["https://api.example.com", "https://admin.example.com"],
"exp": 1735689600,
"nbf": 1735603200,
"iat": 1735603200,
"jti": "unique-token-id-abc123",
// 公共声明(假设已在 IANA 注册)
"name": "张三",
"email": "zhangsan@example.com",
// 私有声明(双方约定)
"user_role": "admin",
"permissions": ["read:users", "write:users"],
"session_id": "session_xyz789"
}
Payload 大小控制: JWT 通常放在 HTTP Header 中传输,而 Header 大小有限制(如 Nginx 默认 8KB)。Payload 应尽量精简,只存放必要的数据。过大的 Payload 会导致请求失败或性能下降。
4.6 声明校验的契约化设计
在 JWT 契约校验中,声明校验规则必须作为契约的一部分明确约定。以下是设计校验规则时的关键考量:
- 必选与可选: 明确哪些声明是必须存在的(如
exp、sub),哪些是可选的(如jti)。 - 校验逻辑: 定义每个声明的具体校验逻辑。例如,
aud是精确匹配还是包含匹配?iss是否支持多个合法签发者? - 异常处理: 当声明校验失败时,应返回统一的错误码和错误信息(如 401 Unauthorized + 具体原因)。
- 时钟偏差: 为
exp和nbf设置可配置的时钟偏差值。
通过将 Payload 声明及其校验规则契约化,可以确保 JWT 在分布式系统中的安全性和一致性,避免因实现差异导致的安全漏洞。