米格速压
JWT 调试 鉴权

JWT 调试常踩的 5 个坑:过期 / 签名 / 时区 / 明文 / 刷新

Token 明明刚发的就提示过期、签名验证总失败、Payload 里的敏感信息被人看到 —— JWT 调试最常见的 5 个坑。讲清楚 JWT 的结构、每个坑的原因和排查方法。

米格速压
2026-05-158 分钟
分享

联调登录接口,后端发的 Token 我这边一用就提示"token 已过期"。 Token 明明是 10 秒前刚拿到的。查了一圈,最后发现是后端那台服务器的系统时间快了 5 分钟 —— Token 的"签发时间"在我这边看来是"5 分钟后",直接被判定异常。

JWT 是现在最主流的鉴权方式,但调试起来坑不少。这篇讲清楚 JWT 的结构, 以及调试时最常踩的 5 个坑。

先搞清 JWT 的结构

一个 JWT 长这样:xxxxx.yyyyy.zzzzz,用点号分成三段:

  • Header(头部):Base64URL 编码的 JSON,说明用什么算法(HS256 / RS256)
  • Payload(载荷):Base64URL 编码的 JSON,放实际数据(用户 ID / 过期时间等)
  • Signature(签名):用密钥对前两段签名,防篡改

关键认知:前两段只是 Base64 编码,不是加密。 任何人拿到 Token 都能解码看到 Header 和 Payload 内容,签名只保证"没被改过"。

坑 1:在 Payload 里放敏感信息

最严重的安全坑。因为 Payload 只是 Base64 编码(不是加密), 任何拿到 Token 的人都能解码看到里面的内容。

绝对不能放进 Payload 的:

  • 密码 / 密码 hash
  • 身份证号 / 银行卡号
  • 手机号(完整的)
  • 任何不希望被用户看到的内部数据

可以放的:用户 ID、用户名、角色、过期时间这些"公开了也无所谓"的标识。

坑 2:Token 刚发就过期(时钟不同步)

就是我开头遇到的坑。JWT 的 exp(过期时间)和 iat(签发时间) 都是 Unix 时间戳。如果签发服务器和验证服务器的时钟不同步,Token 一发出来就"已过期"。

怎么排查

  1. 用 JWT 解析工具看 Payload 里的 iatexp
  2. 把它们转成可读时间,对比当前时间
  3. 如果 iat 是"未来时间",就是签发服务器时钟快了

怎么解决

  • 所有服务器用 NTP 同步时钟
  • 验证时设置时钟偏移容忍(leeway,通常 30-60 秒)

坑 3:签名验证失败

签名验证失败的常见原因:

  • 密钥不一致:签发和验证用了不同的 secret(最常见,检查环境变量)
  • 算法不匹配:签发用 HS256,验证按 RS256
  • Token 被截断:Token 放 URL 里没编码,+ / 被改
  • 复制时混入空格 / 换行:从日志复制 Token 时带了多余字符

排查顺序

  1. 用 JWT 解析工具看 Header 里的 alg(算法)
  2. 确认验证方用的是同一个算法
  3. 确认签发和验证用的是同一个密钥
  4. 检查 Token 字符串有没有被截断 / 混入空白

坑 4:HS256 vs RS256 选错

两种主流算法,适用场景不同:

  • HS256(对称):签发和验证用同一个密钥。简单快,适合单体应用
  • RS256(非对称):私钥签发、公钥验证。适合分布式 / 微服务 (认证服务持有私钥,业务服务用公钥验证,不用共享私钥)

选错的后果:单体项目用 RS256 徒增复杂度;微服务用 HS256 要在所有服务间共享密钥,泄露风险高。

坑 5:Token 无法撤销 / 续期处理不当

JWT 是无状态的:签发后到期前一直有效,服务端无法直接"撤销"。 这导致两个问题:

问题:用户登出 / 改密码后旧 Token 还能用

解决方案:

  • 黑名单:登出的 Token 加入黑名单,验证时检查(牺牲了部分无状态性)
  • 短期 access + 长期 refresh:access token 短期(15 分钟),登出让 refresh token 失效
  • 版本号:Payload 放 token 版本号,改密码时递增版本号,旧 Token 失效

问题:Token 过期后怎么续期

双 Token 机制:

  • access token:短期(15 分钟-1 小时),用于实际请求
  • refresh token:长期(7-30 天),只用于换新 access token

access token 过期时,前端用 refresh token 静默换新的,用户无感知。 refresh token 也过期才要求重新登录。

JWT 调试的标准流程

  1. 把 Token 粘到解析工具,看 Header / Payload 内容
  2. 检查 Payload:有没有放敏感信息(安全审计)
  3. 检查 iat / exp:时间是否合理,转成可读时间对比
  4. 检查 alg:算法是否和验证方一致
  5. 检查 Token 完整性:有没有被截断 / 混入空白字符

总结

JWT 调试的 5 个坑:Payload 放敏感信息(安全)、时钟不同步(刚发就过期)、签名验证失败(密钥/算法)、 算法选错(HS256/RS256)、撤销和续期处理不当。记住 JWT 不是加密,Payload 任何人可见

站里的JWT 解析工具 一键解开 Header / Payload / 签名,自动识别 iat / exp 时间戳并显示过期状态,纯本地不上传 Token。 Token 里的时间戳看不懂,配合时间戳时区那篇理解。

常见疑问

JWT 是加密的吗?里面的内容安全吗?
JWT(JWS)不是加密,是"签名"。它的三段 Header.Payload.Signature,前两段只是 Base64URL 编码,任何人拿到 Token 都能解码看到 Payload 内容。签名只保证"内容没被篡改",不保证"内容保密"。所以<strong>绝对不能在 Payload 里放密码、身份证号等敏感信息</strong>—— 这些会被任何拿到 Token 的人看到。
Token 刚发就提示过期是怎么回事?
99% 是服务器和客户端时钟不同步。JWT 的 exp(过期时间)和 iat(签发时间)是 Unix 时间戳,如果签发服务器的时钟比验证服务器快几分钟,Token 一发出来在验证方看就"已经过期"。解决:① 服务器统一用 NTP 同步时钟;② 验证时留一点"时钟偏移容忍"(leeway,通常 30-60 秒)。
签名验证总是失败?
几个常见原因:① 密钥不一致(签发和验证用了不同的 secret);② 算法不匹配(签发用 HS256 验证按 RS256);③ Token 在传输中被截断(URL 里没编码,+ / 被改);④ 复制 Token 时多了空格 / 换行。排查:先用 JWT 解析工具看 Header 里的算法,确认验证方用的是同一个算法和密钥。
HS256 和 RS256 怎么选?
HS256(对称):签发和验证用同一个密钥,简单快,适合单体应用(同一个服务签发和验证)。RS256(非对称):用私钥签发、公钥验证,适合分布式(认证服务签发,多个业务服务用公钥验证,不用共享私钥)。如果是单体项目用 HS256,微服务 / 第三方验证用 RS256。
Token 应该存哪里?localStorage 还是 Cookie?
各有权衡。localStorage:简单,但易受 XSS 攻击(任何 JS 都能读)。httpOnly Cookie:防 XSS(JS 读不到),但要防 CSRF。现代推荐:① 敏感场景用 httpOnly + Secure + SameSite Cookie;② 配合短过期时间的 access token + 长过期的 refresh token。绝对不要把 Token 放 URL 参数里(会被日志 / 浏览器历史记录)。
Token 过期了怎么自动续期?
用双 Token 机制:① access token 短期(15 分钟-1 小时),用于实际请求;② refresh token 长期(7-30 天),只用于换新的 access token。access token 过期时,前端用 refresh token 静默换一个新的 access token,用户无感知。refresh token 也过期了才要求重新登录。
怎么让 Token 提前失效(比如用户登出 / 改密码)?
JWT 的难点:它是无状态的,签发后到期前一直有效,服务端无法直接"撤销"。解决方案:① 维护一个"黑名单"(登出的 Token 加入黑名单,验证时检查);② 用短过期 access token + refresh token,登出时让 refresh token 失效;③ 在 Payload 里放 token 版本号,改密码时递增版本号使旧 Token 失效。完全无状态和"可撤销"是有取舍的。

看完即用

JWT 解析

解析 JWT Token,查看 Header / Payload / 签名 + 过期时间

立即免费使用
作者
米格速压

米格速压编辑组,专注于办公文件处理场景的教程编写。每周二、五更新。