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 后,对注册声明进行校验的典型流程:

JWT 注册声明校验流程 JWT 注册声明校验流程 接收 JWT 校验 iss(签发者) 校验 sub(主题) 校验 aud(受众) 校验 exp(过期时间) 校验 nbf(生效时间) 校验 jti(唯一标识)

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 在分布式系统中的安全性和一致性,避免因实现差异导致的安全漏洞。