4. JWT Payload 详解:注册声明、公共声明、私有声明与安全注意事项

JWT 的 Payload(载荷)是令牌中承载实际数据的部分,它由一组声明(Claims)构成。声明是关于实体(通常是用户)和附加数据的陈述。理解 Payload 的结构与安全约束,是构建可靠 SaaS 鉴权链路的基础。

4.1 声明分类与结构

JWT 规范将 Payload 中的声明分为三类:注册声明(Registered Claims)、公共声明(Public Claims) 和 私有声明(Private Claims)。这三类声明在用途、命名空间和标准化程度上存在显著差异。

声明类型 命名空间 标准化程度 典型用途
注册声明 IANA 注册,3 字符短名 强制/推荐 令牌生命周期控制(过期、签发者、受众)
公共声明 URI 或命名冲突避免 可选 跨系统共享的标准化用户属性
私有声明 双方约定,无全局注册 自定义 应用内部业务数据(角色、权限、租户 ID)

4.2 注册声明详解

注册声明是 JWT 规范预定义的一组声明,用于提供令牌的元数据和生命周期管理。虽然并非所有注册声明都是强制性的,但 exp 和 iss 在生产环境中几乎必须使用。

  • iss(Issuer):签发者。标识签发该 JWT 的实体,通常是 SaaS 平台的认证服务域名或唯一标识。接收方应验证 iss 是否与预期值匹配,防止跨域令牌滥用。
  • sub(Subject):主题。标识 JWT 所代表的用户或实体的唯一 ID。在 SaaS 中,通常是用户 UUID 或内部用户 ID。sub 在整个签发者范围内应保持唯一。
  • aud(Audience):受众。标识 JWT 的目标接收方。可以是单个服务名、资源标识符或一组 URI。接收方必须验证 aud 是否包含自身标识,防止令牌被其他服务冒用。
  • exp(Expiration Time):过期时间。UNIX 时间戳格式,表示令牌在此时间后失效。接收方必须严格校验 exp,拒绝已过期的令牌。SaaS 中建议设置为 15 分钟到 2 小时。
  • nbf(Not Before):生效时间。UNIX 时间戳格式,表示令牌在此时间之前不可用。用于处理时钟偏差或延迟签发场景。
  • iat(Issued At):签发时间。UNIX 时间戳格式,记录令牌的签发时刻。可用于令牌年龄计算、刷新策略判断和审计追踪。
  • jti(JWT ID):令牌唯一标识。用于防止令牌重放攻击,服务端可维护已使用的 jti 列表。
时钟偏差处理: 在验证 exp 和 nbf 时,建议允许 30-60 秒的时钟偏差(leeway),以容忍分布式系统中各节点时间不同步的问题。但偏差值不应过大,否则会削弱令牌安全性。

4.3 公共声明与私有声明

公共声明 通常使用 URI 格式的声明名(如 https://api.example.com/claims/role)来避免命名冲突。它们用于在多个系统或组织间共享标准化数据。常见的公共声明包括用户邮箱、姓名、头像 URL 等。

私有声明 是 SaaS 应用最常用的声明类型,用于承载业务逻辑所需的自定义数据。私有声明名应避免与注册声明或公共声明冲突,通常使用有意义的短名称。

// 典型的 SaaS JWT Payload 示例
{
  "iss": "https://auth.saas-platform.com",
  "sub": "user_8a3f7b2c1d",
  "aud": ["https://api.saas-platform.com", "https://admin.saas-platform.com"],
  "exp": 1718000000,
  "iat": 1717996400,
  "nbf": 1717996400,
  "jti": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  // 私有声明
  "tenant_id": "tenant_xyz_001",
  "role": "org_admin",
  "permissions": ["read:users", "write:projects", "delete:reports"],
  "session_id": "sess_20240501_abc123"
}

4.4 Payload 安全注意事项

Payload 仅经过 Base64Url 编码,并未加密。任何持有令牌的人都可以解码并读取 Payload 内容。因此,Payload 的安全设计必须遵循以下原则:

核心安全原则: 永远不要在 JWT Payload 中存储敏感信息。密码、信用卡号、个人身份信息(PII)、API 密钥等必须通过其他安全通道传输,或使用 JWE(JSON Web Encryption)进行加密。

4.4.1 数据最小化原则

  • 只包含验证和授权所必需的最小数据集。
  • 避免存储完整的用户对象、地址、电话号码等非必要信息。
  • 使用引用(如用户 ID)代替完整数据,服务端通过 ID 查询详细信息。

4.4.2 声明验证清单

验证项 检查内容 安全影响
iss 验证 签发者是否在受信任列表 防止第三方伪造令牌
aud 验证 当前服务是否在受众列表中 防止令牌跨服务滥用
exp 验证 当前时间是否早于过期时间 防止过期令牌重放
nbf 验证 当前时间是否晚于生效时间 防止提前使用令牌
jti 去重 令牌 ID 是否已被使用 防止重放攻击

4.4.3 常见安全陷阱

  • Payload 过大: 包含过多声明会导致令牌体积膨胀,增加网络传输开销和签名/验证时间。建议 Payload 总大小控制在 1KB 以内。
  • 依赖客户端数据: 永远不要信任客户端提交的 Payload 内容。所有授权决策必须基于服务端验证后的声明。
  • 忽略 aud 验证: 在多服务 SaaS 架构中,不验证 aud 会导致一个服务的令牌被其他服务接受,扩大攻击面。
  • 使用可预测的 sub: 避免使用自增数字作为 sub,建议使用 UUID 或哈希值,防止用户枚举攻击。
最佳实践: 在 SaaS 多租户场景中,将 tenant_id 作为私有声明放入 Payload,并在每次 API 请求中验证该租户 ID 与请求资源所属租户一致,防止跨租户数据泄露。

4.5 Payload 结构示意图

下图展示了 JWT Payload 中三类声明的组织关系,以及它们在令牌验证流程中的角色:

JWT Payload 声明结构 注册声明 (标准化,IANA 注册) iss: 签发者 sub: 主题(用户 ID) aud: 受众 exp: 过期时间 nbf: 生效时间 iat: 签发时间 公共声明 (URI 命名,跨系统共享) email: user@example.com name: 张三 picture: https://... locale: zh-CN updated_at: 1717996400 私有声明 (自定义,应用内部) tenant_id: tenant_xyz role: org_admin permissions: [...] session_id: sess_... department: engineering ⚠ Payload 仅 Base64Url 编码,不加密。禁止存放密码、密钥、PII 等敏感数据。

4.6 Payload 验证流程

服务端在接收到 JWT 后,必须按照严格的顺序验证 Payload 中的声明。下图展示了完整的验证决策流程:

Payload 声明验证流程 接收 JWT 验证 iss(签发者) 验证 aud(受众) 验证 exp / nbf(时间) 授权通过
验证顺序建议: 先验证 iss 和 aud 确保令牌来源和用途正确,再验证 exp/nbf 确保令牌时效性,最后提取私有声明进行业务授权。任何一步失败都应立即拒绝请求,不继续后续验证。