← 返回题目列表

JWT 应验证哪些字段?Refresh Token 如何安全轮换?

高频 困难 第 13 / 23 题 更新于 2026/07/25
JWTRefresh TokenToken 轮换

简化版

JWT 不能只验签名,还要校验:算法(用白名单,不信 header 里的 alg)、签名密钥(多密钥用 kid 选)、iss(发行方)、aud(受众/该 token 是不是发给我这个服务的)、exp(未过期)、nbf(已生效),以及业务声明和用户状态(权限、租户、是否被封禁)。Refresh Token(换取新 access token)应长期但可撤销,采用轮换(rotation)+ 复用检测——每次刷新签发新的、旧的立即失效;如果旧 refresh token 又被使用,说明可能被盗,要废掉整个会话族

详细版

JWT 校验清单(不只是验签):

校验项说明
签名token 未被篡改(第一关,但不够)
算法白名单服务端固定允许的算法,不信 header 的 alg
密钥(kid)多密钥场景按 kid 选,且密钥来源可信
iss发行方是可信的授权服务器
aud受众包含当前资源服务(token 是发给我的)
exp未过期
nbf已生效(不晚于当前时间)
业务声明/状态权限、租户、用户是否被封禁(可能和 DB 不一致)

Access Token vs Refresh Token:

Access TokenRefresh Token
用途频繁访问 API只用于换新 access token
生命周期短(分钟级)长(天级)
泄露窗口需可撤销、可检测
存放内存/Authorization 头服务端存哈希、HttpOnly Cookie/安全存储

完整版教学

一、签名只是第一关

很多人以为「JWT 验了签名就安全了」——远远不够验签只能说明两件事:token 没被篡改、来自持有某个密钥的一方。但它不能说明这个 token 适合当前服务

  • 可能是别的系统用同一套签名机制签发的 token。
  • 可能已经过期
  • 可能是发给另一个服务的 token(aud 不对)。

所以资源服务器验签之后,还必须校验发行方、受众、有效期、算法和业务声明,否则可能接受了不该接受的 token。签名是「防伪」,其他声明是「防误用」。

校验层次典型字段防住的问题
加密完整性签名、算法白名单、kid篡改、算法混淆、错密钥
标准声明issaudexpnbf错发行方、错服务、过期或未生效
业务状态权限、租户、用户状态、token version权限过期、封禁未生效、跨租户

JWT 校验要按“签名防伪、声明防误用、状态防过期权限”三层记,少一层都可能出安全洞。

二、算法和密钥选择(安全陷阱)

  • 固定允许的算法(算法白名单):服务端应该写死自己接受的算法(如只接受 RS256),不能完全相信 token header 里的 alg。历史上有著名的 alg=none 攻击(攻击者把算法改成 none、去掉签名)和 RS256→HS256 混淆攻击(把非对称改成对称、用公钥当密钥伪造签名)。防护就是服务端固定算法,忽略 header 的 alg 主张
  • 多密钥用 kid:如果有多个签名密钥(轮换期间),token header 的 kid 指明用哪个密钥验,但仍要确认这个密钥来源可信(从可信的 JWKS 端点获取)。
  • 非对称签名的优势RS256/ES256 等非对称算法——授权服务持私钥签发,多个资源服务只需公钥验证。公钥可以公开分发,私钥集中保管,适合多服务架构。

三、标准声明校验(时间与来源)

几个标准声明必须校验:

  • iss(issuer):发行方,要匹配你信任的授权服务器
  • aud(audience):受众,要包含当前资源服务的标识——确认「这个 token 是发给我这个服务用的」,防止拿着发给 A 服务的 token 去访问 B 服务。
  • exp(expiration)不能过期
  • nbf(not before)不能晚于当前时间(token 还没生效不能用)。
  • 必要时校验 iat(签发时间)、jti(token 唯一 id,可用于防重放)

时间校验可允许很小的时钟偏差(clock skew,如几十秒,容忍服务器间时钟不完全同步),但不能无限放宽

now = 10:00:00, clock skew = 60s
nbf <= 10:01:00 才可接受
exp >= 09:59:00 才可接受

四、业务声明不能盲信(关键)

JWT 里常写入角色、权限、租户、用户状态等业务声明。但这些是「签发那一刻」的快照,在 token 过期前,可能和数据库最新状态不一致

  • 用户在 token 有效期内被封禁了,但他手里的 token 还没过期——如果只信 token 里的声明,他还能继续访问
  • 用户的权限被收回了,token 里的旧权限还在。

所以对高风险操作(转账、删除、改配置),不能只信 token 里的声明——应该实时查数据库确认用户状态、权限版本(或 token version)。这是 JWT「无状态」的固有代价:无状态换来了性能和扩展性,但牺牲了「实时撤销」的能力,高风险场景要用实时校验补回来。

五、Access Token 和 Refresh Token 的分工

双 token 机制是 JWT 认证的标准做法:

  • Access Token短命(几分钟~几十分钟),频繁用于访问 API。因为泄露窗口要尽量小——即使被偷,很快就过期。
  • Refresh Token长命(几天~几周),只用于换取新的 access token不直接访问业务 API。它必须可撤销、可审计、可检测异常

关键不要把 refresh token 到处传给资源服务——它只在「刷新」这一个端点用,减少暴露面。access token 短命降低泄露风险,refresh token 通过可撤销来兜底。

六、轮换和复用检测(refresh token 安全核心)

Refresh Token 轮换(Rotation)每次用 refresh token 刷新时,签发一个新的 refresh token,并让旧的立即失效

复用检测(Reuse Detection):因为旧的已失效,如果一个已失效的旧 refresh token 又被使用,这是一个强风险信号——说明它可能被窃取了(攻击者拿旧 token 来刷新),或者发生了异常复用。此时应该撤销整个「会话族」(token family)——把这条会话链上所有 token 全废掉,强制重新登录

并发刷新问题:合法客户端也可能因为并发/重试同时用同一个 refresh token 刷新两次,这会被误判为复用。处理:用短暂宽限期原子状态更新(数据库乐观锁)来区分「合法并发」和「真正的盗用」。

七、存储与客户端安全

  • 服务端不要明文存 refresh token——至少存哈希(类似密码,泄露了也难还原)。
  • 浏览器:token 放 HttpOnly + Secure + SameSite 的 Cookie(防 XSS 读取、防 CSRF);放 localStorage 容易被 XSS 偷(JS 能读)。
  • 移动端:用系统的安全存储(Keychain/Keystore)。
  • 所有 token 必须走 HTTPS(防中间人窃听)。
  • 日志绝不打印完整 token(脱敏)。

八、常见误区与追问

  • 误区:JWT 只要验签通过就安全。 验签只能证明未被篡改,还必须校验算法、发行方、受众、有效期和业务状态。
  • 误区:可以完全相信 token header 里的 alg 服务端必须使用算法白名单,避免 alg=none 或 RS/HS 混淆等历史攻击模式。
  • 误区:Refresh Token 泄露风险比 Access Token 小。 Refresh Token 生命周期长且能换新 token,必须可撤销、轮换、复用检测。
  • 追问:aud 校验解决什么问题? 防止把发给 A 服务的 token 拿去访问 B 服务,确认当前资源服务是 token 的受众。
  • 追问:Refresh Token 复用检测发现旧 token 又被使用怎么办? 应视为疑似泄露,撤销整个 token family 并要求重新登录。
  • 追问:用户被封禁后 JWT 还没过期怎么办? 高风险操作要查用户状态或 token version,不能只依赖 token 中的旧快照。

九、加强记忆

JWT 校验不只验签名——还要验:算法白名单(不信 header 的 alg,防 alg=none / RS256→HS256 攻击)、密钥(kid,来源可信,非对称让授权服务持私钥、资源服务用公钥)、iss(发行方)、aud(受众,token 是发给我的)、exp(未过期)、nbf(已生效)、业务声明/用户状态(可能和 DB 不一致,高风险操作要实时查)Access Token 短命(频繁访问、泄露窗口小);Refresh Token 长命但可撤销(只用于刷新、别传给资源服务)。轮换 + 复用检测:每次刷新签发新的、旧的立即失效,旧 refresh token 再被用 = 疑似被盗 → 撤销整个会话族强制重登(并发刷新用宽限期/原子更新区分)。存储:服务端存哈希、浏览器用 HttpOnly Cookie(别放 localStorage)、走 HTTPS、日志不打完整 token