4. JWT 载荷详解:注册声明、公共声明、私有声明
JWT 的第二部分——载荷(Payload)是承载实际数据的核心区域。它由三组声明(Claims)构成:注册声明、公共声明和私有声明。理解这三者的定义、用途和约束,是正确设计 JWT 认证与授权契约的基础。
4.1 注册声明(Registered Claims)
注册声明是 JWT 标准(RFC 7519)预定义的一组非强制性但强烈推荐的声明。它们提供了互操作性,确保不同系统之间能够理解相同的语义。以下是七个核心注册声明:
| 声明名 | 全称 | 类型 | 说明 |
|---|---|---|---|
| iss | Issuer | 字符串或 URI | 签发该 JWT 的主体。通常是服务端的唯一标识,如 https://auth.example.com。 |
| sub | Subject | 字符串或 URI | JWT 所描述的主体,通常是用户 ID 或唯一标识。在整个发行商范围内唯一。 |
| aud | Audience | 字符串或数组 | JWT 的接收方。标识该令牌预期给谁使用,可以是单个服务名或一组服务。 |
| exp | Expiration Time | NumericDate | 过期时间。在此时间之后,JWT 不得被接受处理。格式为 Unix 时间戳。 |
| nbf | Not Before | NumericDate | 生效时间。在此时间之前,JWT 不得被接受处理。 |
| iat | Issued At | NumericDate | 签发时间。标识 JWT 是什么时候被创建的。 |
| jti | JWT ID | 字符串 | 令牌的唯一标识符。用于防止重放攻击,服务端可记录已使用的 jti。 |
实战建议:在微服务架构中,
aud 声明尤其重要。例如,用户服务签发的令牌应明确指定 aud: ["order-service", "payment-service"],防止令牌被非预期的服务滥用。
4.2 公共声明(Public Claims)
公共声明是由 IANA JSON Web Token Claims 注册表定义的声明,或者使用 URI 作为名称以避免冲突。它们通常用于传递标准化的业务信息,例如用户角色、权限范围等。
- 标准化公共声明:如
scope(权限范围)、client_id(客户端标识)等,已在 IANA 注册。 - 自定义公共声明:使用 URI 格式命名,例如
https://api.example.com/claims/role,确保全球唯一性。
关键区别:公共声明与私有声明的核心区别在于命名空间的管理。公共声明通过注册或 URI 避免冲突,而私有声明仅在双方之间约定,不保证全局唯一。
4.3 私有声明(Private Claims)
私有声明是 JWT 签发方和接收方之间私下约定的声明。它们不经过注册,也不使用 URI 命名空间。私有声明适合传递内部业务数据,例如用户昵称、部门 ID、临时会话状态等。
{
"sub": "user_12345",
"iss": "auth.example.com",
"exp": 1718000000,
"iat": 1717913600,
"role": "admin", // 私有声明
"department": "engineering", // 私有声明
"session_id": "abc-123-def" // 私有声明
}
安全警告:私有声明不应包含敏感信息(如密码、信用卡号),因为 JWT 载荷仅经过 Base64 编码,并非加密。任何拥有令牌的人都可以解码查看。
4.4 声明使用对比
| 维度 | 注册声明 | 公共声明 | 私有声明 |
|---|---|---|---|
| 命名方式 | 预定义三字符名称 | IANA 注册或 URI | 双方约定名称 |
| 全局唯一性 | 是 | 是(通过注册或 URI) | 否 |
| 适用场景 | 令牌生命周期管理 | 标准化业务属性 | 内部业务数据 |
| 互操作性 | 高 | 中 | 低(仅限约定双方) |
| 示例 | exp, sub |
scope, https://example.com/role |
nickname, org_id |
4.5 声明校验流程
服务端在接收到 JWT 后,必须对关键注册声明进行校验,以确保令牌的有效性和安全性。以下是标准校验步骤:
- 校验
exp:当前时间必须早于exp值,否则令牌已过期。 - 校验
nbf:当前时间必须晚于或等于nbf值,否则令牌尚未生效。 - 校验
iss:签发者必须与信任列表中的值匹配。 - 校验
aud:当前服务必须出现在aud列表中。 - 校验
jti:检查该jti是否已被使用(防重放)。
校验顺序建议:先校验
exp 和 nbf(时间相关),再校验 iss 和 aud(身份相关),最后校验 jti(唯一性相关)。这样可以尽早拒绝无效令牌,减少计算开销。
4.6 实战案例:构建一个完整的 JWT 载荷
以下是一个包含注册声明、公共声明和私有声明的完整 JWT 载荷示例,用于一个电商平台的订单服务:
{
"iss": "https://auth.shop.example.com",
"sub": "user_98765",
"aud": ["order-service", "payment-service"],
"exp": 1718096400,
"nbf": 1717913600,
"iat": 1717913600,
"jti": "unique-token-id-20240601",
"scope": "read:orders write:orders",
"https://shop.example.com/claims/role": "premium_member",
"nickname": "张三",
"department_id": "dept_ord_01"
}
解析:
iss和aud确保令牌只能由认证中心签发,且仅用于订单和支付服务。exp和nbf控制令牌的有效时间窗口。jti用于防止重放攻击,服务端需维护已使用列表。scope(公共声明)定义权限范围。nickname和department_id(私有声明)传递内部业务数据。
4.7 常见误区与避坑指南
- 误区一:认为所有声明都是必须的。实际上,只有
exp在大多数安全场景中被强烈推荐,其他声明按需使用。 - 误区二:在私有声明中存放密码或密钥。JWT 载荷仅 Base64 编码,任何持有令牌的人都可以解码查看。
- 误区三:忽略
aud校验。在多服务架构中,不校验aud可能导致令牌被非预期服务滥用。 - 误区四:使用
iat代替nbf做生效控制。iat仅表示签发时间,不应作为生效判断依据。
关键提醒:始终在服务端对
exp、nbf、iss、aud 进行严格校验。忽略其中任何一个,都可能导致安全漏洞或业务逻辑错误。