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

JWT 的 Payload(载荷)是令牌中承载实际数据的部分,其结构是一个 Base64Url 编码的 JSON 对象。根据 RFC 7519 规范,Payload 中的声明(Claim)被划分为三类:注册声明(Registered Claims)、公共声明(Public Claims) 和 私有声明(Private Claims)。理解这三类声明的边界与用途,是构建安全、可互操作 JWT 系统的基础。

核心原则: Payload 仅做声明(Claim)的载体,不应对敏感数据(如密码、完整信用卡号)进行明文存储。即使 JWT 使用签名防止篡改,Payload 本身仅做 Base64Url 编码,并非加密。敏感信息必须通过 JWE(JSON Web Encryption)或额外加密层保护。

4.1 注册声明(Registered Claims)

注册声明是 RFC 7519 预定义的一组标准化声明名称,用于提供通用的、可互操作的语义。它们并非强制使用,但强烈建议在适用场景下采用,以提升跨系统的兼容性。以下是七个核心注册声明:

声明名 全称 类型 语义与约束 典型值示例
iss Issuer 字符串 / URI 签发该 JWT 的主体。验证方应校验 iss 是否与信任的签发方一致。 "https://auth.example.com"
sub Subject 字符串 JWT 所描述的主体(通常是用户 ID)。在同一个 iss 范围内应全局唯一。 "user_123456"
aud Audience 字符串 / 字符串数组 JWT 的接收方。验证方必须确认自身在 aud 列表中,否则应拒绝。 "api-gateway" 或 ["service-a", "service-b"]
exp Expiration Time NumericDate 过期时间戳(秒)。必须在 exp 之后拒绝该令牌。 1735689600 (2025-01-01 00:00:00 UTC)
nbf Not Before NumericDate 生效时间戳(秒)。在该时间之前,令牌不应被接受。 1735603200
iat Issued At NumericDate 签发时间戳(秒)。用于判断令牌的“年龄”或进行日志审计。 1735600000
jti JWT ID 字符串 令牌的唯一标识符。用于防止重放攻击(Replay Attack),服务端需维护已使用的 jti 黑名单。 "550e8400-e29b-41d4-a716-446655440000"
时间声明安全要点:
  • 时钟偏差(Clock Skew): 验证服务应允许最多 30-60 秒的时钟偏差,避免因服务器时间不同步导致合法令牌被误拒。
  • exp 与 nbf 组合: 建议同时使用 exp 和 nbf,形成严格的时间窗口。例如:nbf: 1735603200, exp: 1735689600。
  • jti 生命周期: 服务端只需在 exp 之前维护 jti 黑名单,过期后可自动清理,避免存储无限增长。
JWT 注册声明时间轴与生命周期 iat 签发时间 nbf 生效时间 令牌不可用(等待生效) exp 过期时间 令牌有效区间 令牌已过期 jti(JWT ID)在令牌有效期内唯一,用于防重放;过期后可清理黑名单。

4.2 公共声明(Public Claims)

公共声明是由 IANA JSON Web Token Claims Registry 注册的声明名称,或使用 Collision-Resistant Namespace(抗冲突命名空间)定义的声明。它们用于在开放生态中共享标准化的业务语义,避免不同系统间的命名冲突。

  • IANA 注册声明: 例如 name、email、picture、email_verified 等。这些声明由 IANA 维护,任何系统都可以引用,具有明确的语义。
  • 抗冲突命名空间: 使用 URI 或域名作为声明名称的前缀。例如:"https://example.com/claims/role" 或 "com.example.tenant_id"。这种方式确保声明名称在全球范围内唯一。
最佳实践: 如果你的系统需要定义新的、可能被外部系统使用的声明,优先考虑注册到 IANA 或使用 URI 命名空间。避免使用简单字符串(如 "role"),因为可能与未来其他系统的公共声明冲突。

4.3 私有声明(Private Claims)

私有声明是 JWT 签发方和验证方之间私下约定的声明名称。它们不经过 IANA 注册,也不使用命名空间。私有声明适用于内部系统、微服务间通信或特定业务场景。

  • 使用场景: 内部用户角色、权限标识、会话 ID、租户 ID、请求追踪 ID 等。
  • 风险: 命名冲突。如果两个不同的内部系统使用了相同的私有声明名称但语义不同,可能导致安全漏洞或逻辑错误。
  • 缓解措施: 在团队或组织内部建立声明命名规范,例如统一前缀(如 app_、svc_)。
// 示例:包含三类声明的 JWT Payload
{
  // 注册声明
  "iss": "https://auth.example.com",
  "sub": "user_789012",
  "aud": ["api-gateway", "order-service"],
  "exp": 1735689600,
  "nbf": 1735603200,
  "iat": 1735600000,
  "jti": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",

  // 公共声明(IANA 注册)
  "name": "张三",
  "email": "zhangsan@example.com",
  "email_verified": true,

  // 私有声明(内部约定)
  "app_role": "admin",
  "app_tenant_id": "tenant_42",
  "app_session_id": "sess_abc123"
}

4.4 三类声明的对比与选择

维度 注册声明 公共声明 私有声明
标准化程度 RFC 标准,全球通用 IANA 注册或 URI 命名空间 内部约定,无标准化
命名冲突风险 极低(RFC 保留) 低(URI 保证唯一性) 高(需内部规范)
互操作性 高(所有 JWT 库支持) 中(需双方理解语义) 低(仅限约定双方)
适用场景 令牌生命周期、身份标识 用户画像、开放 API 标准字段 内部业务逻辑、微服务上下文
推荐度 必须使用(时间相关) 推荐(开放生态) 谨慎使用(需规范)
JWT 三类声明架构边界与交互 JWT Token 边界 注册声明 iss, sub, aud exp, nbf, iat jti RFC 7519 标准 所有 JWT 库原生支持 公共声明 name, email, picture email_verified https://example.com/role IANA 注册 / URI 命名空间 开放生态互操作 私有声明 app_role app_tenant_id app_session_id 内部约定 需命名规范防冲突 三类声明共存于同一 Payload,互不冲突;验证方按需提取对应声明。

4.5 Payload 安全设计原则

  1. 最小化原则: Payload 只包含验证和授权所必需的声明。避免携带大量业务数据,以减少令牌体积和泄露风险。
  2. 敏感数据保护: 密码、密钥、完整身份证号等敏感信息严禁放入 Payload。如需传递,使用 JWE 加密整个 JWT 或对敏感字段单独加密。
  3. 声明验证: 服务端必须对 iss、aud、exp、nbf 等关键声明进行严格校验。忽略这些校验是 JWT 安全漏洞的常见来源。
  4. 声明命名规范: 私有声明使用统一前缀(如 app_、svc_),公共声明优先使用 IANA 注册名称或 URI 命名空间。
  5. 时间戳精度: 使用 NumericDate(秒级 Unix 时间戳),避免使用字符串格式的时间,以减少解析歧义。
实战建议: 在微服务架构中,建议将 aud 声明设置为目标服务的标识列表。例如,一个令牌同时用于访问订单服务和支付服务时,aud 可设为 ["order-service", "payment-service"]。每个服务在验证时只接受包含自身标识的令牌,实现细粒度的服务间授权。