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 中三类声明的组织关系,以及它们在令牌验证流程中的角色:
4.6 Payload 验证流程
服务端在接收到 JWT 后,必须按照严格的顺序验证 Payload 中的声明。下图展示了完整的验证决策流程:
验证顺序建议: 先验证 iss 和 aud 确保令牌来源和用途正确,再验证 exp/nbf 确保令牌时效性,最后提取私有声明进行业务授权。任何一步失败都应立即拒绝请求,不继续后续验证。