4. JWT 生命周期管理:签发、过期、生效时间与唯一标识

JWT 并非“签发即永久有效”的令牌。在分布式鉴权体系中,必须精确控制令牌的生效窗口与唯一性,否则会引入重放攻击、令牌泄露后无法撤销等严重风险。本章围绕 JWT 载荷中的四个核心字段——iat(签发时间)、exp(过期时间)、nbf(生效时间)、jti(唯一标识)——展开实战级解析。

4.1 字段总览与语义对比

下表列出四个字段的声明位置、数据类型与核心语义,后续各节逐一深入。

字段 全称 数据类型 语义 是否必选(RFC 7519)
iat Issued At NumericDate 令牌签发的时间戳 否(强烈建议)
exp Expiration Time NumericDate 令牌的绝对过期时间点 否(强烈建议)
nbf Not Before NumericDate 令牌最早可被接受的时间点 否
jti JWT ID String 令牌的唯一标识符,防重放 否
时间戳单位:所有 NumericDate 字段均使用 Unix 时间戳(自 1970-01-01 00:00:00 UTC 起的秒数)。服务端校验时需注意时钟同步,建议所有节点使用 NTP 对齐,容忍误差不超过 30 秒。

4.2 iat(签发时间)—— 令牌的出生证明

iat 记录令牌的签发时刻。虽然 RFC 7519 未强制要求,但在以下场景中不可或缺:

  • 令牌老化分析:通过 iat 与 exp 的差值计算令牌剩余有效期,用于客户端提前刷新。
  • 黑名单时间窗口:当令牌被撤销时,仅需拒绝 iat 早于撤销时间的令牌,无需存储所有已签发令牌。
  • 审计溯源:日志中记录 iat 可定位签发服务实例与签发时刻。
// 签发时设置 iat(Go 示例)
claims := jwt.MapClaims{
  "iat": time.Now().Unix(),
  "exp": time.Now().Add(1 * time.Hour).Unix(),
  "sub": "user_1001",
}
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
signedToken, _ := token.SignedString([]byte("secret"))

4.3 exp(过期时间)—— 令牌的生命终点

exp 是 JWT 生命周期中最关键的字段。服务端在验证 JWT 时,必须检查当前时间是否已超过 exp,超过则直接拒绝。

常见陷阱:不要依赖客户端删除过期令牌。攻击者可能持有已签发的令牌副本,服务端必须做 exp 校验。另外,exp 是绝对时间,不要设置为“相对过期时长”,必须转换为 Unix 时间戳。

过期策略对比:

策略 典型有效期 适用场景 风险
短过期 + 刷新令牌 15 分钟 ~ 1 小时 Web 应用、移动端 API 需额外管理 refresh token
长过期 + 黑名单 24 小时 ~ 7 天 服务间通信、IoT 设备 黑名单存储压力大
无过期(不推荐) 永久 仅限内部测试 泄露后无法撤销

4.4 nbf(生效时间)—— 令牌的“预热期”

nbf 定义令牌在哪个时间点之后才被接受。常用于以下场景:

  • 定时任务授权:签发一个未来才生效的令牌,用于预授权某个定时操作。
  • 分布式时钟补偿:签发服务与验证服务之间存在已知时钟偏差时,设置 nbf = iat + 5s 避免因时钟不同步导致令牌被提前拒绝。
  • 条件激活:用户付费后,令牌在付费生效时间点才可用。
// 设置 nbf 为签发后 10 秒生效
claims["nbf"] = time.Now().Add(10 * time.Second).Unix()
校验逻辑:验证时需同时检查 nbf 和 exp:
nbf <= current_time < exp。如果 nbf 未设置,则默认令牌立即生效。

4.5 jti(唯一标识)—— 防重放的最后防线

jti 是一个由签发服务生成的唯一字符串,用于标识单个 JWT。即使两个令牌的载荷完全相同,jti 也必须不同。核心用途:

  • 防重放攻击:服务端维护一个已使用 jti 的集合(如 Redis Set),令牌被使用后记录 jti,后续相同 jti 的请求直接拒绝。
  • 令牌撤销:将 jti 加入黑名单,比基于 iat 的撤销更精确。
  • 日志关联:通过 jti 串联同一令牌在多个服务间的调用链路。
// 生成 jti(UUID v4)
import "github.com/google/uuid"

claims["jti"] = uuid.New().String()
性能注意:如果每个请求都查询 Redis 检查 jti 是否已使用,会引入额外延迟。建议仅在关键写操作或高安全场景下启用 jti 校验,读操作可跳过。

4.6 生命周期状态机与校验流程

下图展示 JWT 从签发到废弃的完整状态转换,以及各字段在验证阶段的检查点。

JWT 生命周期状态机与校验流程 JWT 生命周期状态机与校验流程 签发 (iat) 未生效 (nbf) 有效 (exp) 过期 (exp) 已撤销 (jti) 校验入口 校验阶段检查点: 1. 解析 JWT,检查签名是否有效 2. 检查 current_time >= nbf(若 nbf 存在) 3. 检查 current_time < exp(若 exp 存在) 4. 检查 jti 是否在黑名单中(若启用)

4.7 实战建议与常见误区

字段 推荐做法 常见误区
iat 始终设置,用于审计和黑名单时间窗口 使用客户端时间而非服务端时间
exp 必须设置,有效期不超过业务容忍上限 设置为相对时长而非绝对时间戳
nbf 仅在需要延迟生效时设置,默认不设 与 iat 混淆,或设置过大的未来时间
jti 高安全场景必设,使用 UUID v4 使用固定字符串或可预测序列
核心原则:JWT 生命周期管理不是“签发即忘”。iat 定义起点,nbf 定义可用起点,exp 定义终点,jti 提供精确撤销能力。四者配合使用,才能构建安全的分布式鉴权体系。