4、JWT Payload详解:注册声明、公共声明、私有声明

JWT 的 Payload(载荷)是令牌中承载实际数据的部分,它是一个 JSON 对象,包含一组声明(Claims)。声明是关于实体(通常是用户)和附加数据的陈述。根据 JWT 标准(RFC 7519),Payload 中的声明分为三类:注册声明、公共声明和私有声明。理解这三类声明的边界与用途,是设计安全、可互操作的认证授权系统的关键。

核心原则:Payload 的大小直接影响网络传输效率。建议仅包含必要的最小声明集,避免将大量用户数据(如完整用户信息、权限列表)直接放入 JWT,推荐使用引用方式(如用户 ID)并在服务端查询。

4.1 注册声明(Registered Claims)

注册声明是一组预定义的、标准化的声明,旨在提供互操作性。它们不是强制性的,但强烈建议使用,以帮助不同系统之间理解 JWT 的含义。这些声明由 IANA JSON Web Token Claims 注册表维护。

声明名 全称 类型 描述 使用建议
iss Issuer 字符串或 URI 签发该 JWT 的实体标识符。通常是发行方的唯一标识,如 https://auth.example.com。 必须验证,防止令牌被伪造或跨域使用。
sub Subject 字符串或 URI JWT 所描述的主体,通常是用户的唯一标识(如用户 ID)。 必须验证,确保令牌指向正确的用户。
aud Audience 字符串或 URI 数组 JWT 的接收方标识。标识此令牌预期用于哪个或哪些服务。 必须验证,防止令牌被重放到其他服务。
exp Expiration Time 数字(NumericDate) JWT 的过期时间。在此时间之后,JWT 被视为无效。 必须验证,是防止令牌长期泄露的核心机制。
nbf Not Before 数字(NumericDate) JWT 的生效时间。在此时间之前,JWT 不应被接受。 可选验证,用于处理时钟偏差或延迟签发场景。
iat Issued At 数字(NumericDate) JWT 的签发时间。可用于确定令牌的年龄。 建议验证,用于计算令牌剩余有效期或触发刷新。
jti JWT ID 字符串 JWT 的唯一标识符。用于防止重放攻击(Replay Attack)。 建议验证,服务端需维护已使用 jti 的集合。
时间声明验证陷阱:验证 exp 和 nbf 时,必须考虑客户端与服务端之间的时钟偏差(Clock Skew)。通常允许一个小的容差窗口(如 30 秒),但不应过大,否则会削弱安全性。推荐使用 NTP 同步服务器时间。

4.2 公共声明(Public Claims)

公共声明是由使用 JWT 的各方(如组织、公司、开源项目)自定义的声明。为了避免命名冲突,公共声明应该使用 Collision-Resistant Name(防冲突命名)。推荐的做法是使用 URI 作为声明名,例如 https://example.com/claims/role。这样,即使不同组织使用了相同的声明名,由于 URI 的唯一性,也不会发生冲突。

公共声明通常用于传递应用级别的、非敏感的用户属性或上下文信息。

  • 用户角色:https://api.example.com/claims/role → "admin"
  • 用户权限:https://api.example.com/claims/permissions → ["read:users", "write:users"]
  • 用户所属部门:https://org.example.com/claims/department → "engineering"
  • 用户时区:https://app.example.com/claims/timezone → "Asia/Shanghai"
最佳实践:公共声明应避免包含敏感信息(如密码、信用卡号)。JWT 的 Payload 仅经过 Base64Url 编码,并未加密,任何拥有令牌的人都可以解码读取。如果需要传递敏感数据,请使用 JWE(JSON Web Encryption)。

4.3 私有声明(Private Claims)

私有声明是 JWT 的签发方和接收方之间私下约定的声明。它们没有命名空间,因此双方必须事先就声明名称和含义达成一致。私有声明通常用于在信任域内部传递非标准化的、临时的或特定于业务逻辑的数据。

由于没有防冲突机制,私有声明应谨慎使用,尤其是在跨组织或跨系统交互时。如果声明可能被多个系统使用,应将其升级为公共声明。

  • 会话 ID:"session_id": "abc123"
  • 登录来源:"login_source": "oauth2"
  • 临时标志:"is_new_user": true
  • 内部版本号:"version": 2

4.4 三类声明的关系与选择

下图展示了三类声明在 JWT Payload 中的共存关系,以及它们各自的应用边界。

JWT Payload 声明分类与边界 JWT Payload 注册声明 (Registered) 标准化、互操作性 iss: 签发者 sub: 主体 aud: 接收方 exp: 过期时间 nbf: 生效时间 iat: 签发时间 jti: 唯一ID 公共声明 (Public) 防冲突命名 (URI) https://api.example.com/claims/role https://org.example.com/claims/dept 私有声明 (Private) 私下约定、无命名空间 session_id, login_source

4.5 声明验证流程

服务端在接收到 JWT 后,必须对 Payload 中的关键声明进行验证,以确保令牌的合法性和有效性。以下是一个典型的验证流程:

  1. 验证签名:使用公钥或共享密钥验证 JWT 的签名是否与 Header 和 Payload 匹配。这是所有验证的前提。
  2. 验证 iss:检查签发者是否在受信任的签发者列表中。
  3. 验证 aud:检查当前服务是否在令牌的接收方列表中。
  4. 验证 exp:检查当前时间是否早于过期时间(考虑时钟偏差)。
  5. 验证 nbf:如果存在,检查当前时间是否晚于生效时间(考虑时钟偏差)。
  6. 验证 jti:如果存在,检查该 ID 是否已被使用(防止重放)。
  7. 验证 sub:确保主体标识有效且存在于系统中。
验证顺序建议:先验证签名和时间声明(exp、nbf),再验证业务声明(iss、aud、sub、jti)。这样可以尽早拒绝无效令牌,减少不必要的计算开销。

4.6 声明设计对比

特性 注册声明 公共声明 私有声明
标准化程度 高(RFC 标准) 中(建议 URI 命名) 低(双方约定)
命名冲突风险 无(预定义) 低(URI 保证唯一性) 高(无命名空间)
互操作性 强(所有 JWT 库支持) 中(需双方解析 URI) 弱(仅限内部系统)
典型用途 安全控制、令牌生命周期 应用级用户属性 临时或内部业务数据
推荐场景 所有 JWT 都应包含 跨服务共享用户信息 微服务内部通信

4.7 完整 Payload 示例

以下是一个包含三类声明的 JWT Payload 示例:

{
  // 注册声明
  "iss": "https://auth.example.com",
  "sub": "user_1234567890",
  "aud": ["https://api.example.com", "https://admin.example.com"],
  "exp": 1718000000,
  "nbf": 1717996400,
  "iat": 1717996400,
  "jti": "unique-token-id-abc-123",

  // 公共声明
  "https://api.example.com/claims/role": "editor",
  "https://org.example.com/claims/department": "engineering",

  // 私有声明
  "session_id": "sess_xyz_789",
  "login_source": "sso"
}
安全警告:切勿在 Payload 中存储密码、密钥、信用卡号等敏感信息。JWT 的 Payload 仅经过 Base64Url 编码,等同于明文传输。任何中间节点或持有令牌的人都可以轻松解码查看。如需保护数据机密性,请使用 JWE 或通过其他安全通道传递敏感信息。