约 7 分钟读完 · 更新于 2026-10-21 · 文中每条都能在下面的配套工具里动手验证
三段各是 base64url 编码的 JSON,用点号连接。前两段任何人都能解开看(不是加密!),第三段是签名——它保证前两段「一个字节都没被改过」。手动看一眼的方法:前两段各做一次 base64url 解码即可。
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9 ← header {"alg":"HS256","typ":"JWT"}
.eyJzdWIiOiIxMDAxIiwibmFtZSI6IkFsaWNlIiwiZXhwIjoxNzI3NTY4MDAwfQ
← payload {"sub":"1001","name":"Alice","exp":1727568000}
.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c ← 签名(不可解,只能验)最容易踩的坑:exp 只是 payload 里的一个数字,库不会「自动」拒收过期 token——除非你在验证选项里显式开启过期检查。线上事故里「token 永不过期」多半是验证时没配 algorithms 白名单,或 ignoreExpiration 被误设为 true。
| 声明 | 含义 | 谁负责校验 |
|---|---|---|
| iss | 签发者标识 | 接收方比对是否信任的签发方 |
| sub | 主体(通常是用户 ID) | 业务层取用 |
| aud | 目标受众 | 接收方确认 token 是发给自己的 |
| exp | 过期时间(Unix 秒) | 库自动校验——但仅在你启用时 |
| nbf / iat | 生效时间 / 签发时间 | 接收方按需校验 |
| jti | token 唯一编号 | 用于吊销名单(黑名单记 jti) |
任何人拿到 JWT 都能解出完整 payload——手机号、邮箱、角色塞进去等于公开发布。JWT 的价值是「防篡改」:接收方用密钥重算签名比对,改过一个字符就验不过。需要保密的字段不放进 payload,或改用 JWE(加密版 JWT)。
组合拳:access token 短命(5~15 分钟)+ refresh token 长命(可服务端吊销)。access 泄露的损失被短 exp 封顶;refresh 泄露可以立刻在服务端作废。这是「无状态」与「可吊销」之间目前的最佳折中。
jwt.verify(token, PUBLIC_KEY, {
algorithms: ["RS256"], // 白名单写死,不信 token 自述
issuer: "auth.example.com",
audience: "api.example.com",
clockTolerance: 30 // 容忍 30 秒时钟偏差
});Session 把状态放服务端(内存 / Redis),天然可吊销、实现简单,单体应用的默认选择;JWT 把状态放在 token 里,验证方无需查库,适合多服务、跨域与移动端。判断标准一句话:需不需要「立刻踢人下线」?需要 → Session 或 JWT+吊销名单;不需要且验证方多 → JWT。先用 JWT 解析工具看看现有 token 里到底放了什么,是优化的第一步。
早年不少 JWT 库把「token 声明的算法」当真:攻击者把 header 改成 {"alg":"none"} 并删掉签名段,库看到 none 就跳过验签,伪造的 payload 直接生效——多个知名库都中过招。防御只有一条:验证时算法白名单由代码写死,与 token 自述无关。
各有代价:localStorage 方便但 XSS 可读且难吊销;HttpOnly + Secure + SameSite 的 Cookie 抗 XSS 但要防 CSRF,且跨域配置更繁琐。前后端分离 + 移动端通常 localStorage(短 exp + refresh 兜底);传统 Web 应用推荐 HttpOnly Cookie。关键是想清楚你的威胁模型里 XSS 和 CSRF 哪个更近。
按出现频率排:① 密钥不匹配——签发用 HS256、验证却按 RS256 的公钥试,或 secret 复制带了首尾空格;② 算法白名单没包含 token 实际的 alg(库名义上支持,verify 选项里没列);③ iss / aud 断言不符——多环境共用密钥时高发;④ 时钟偏差——两台机器差几十秒,exp / nbf 刚好卡在边界,加 clockTolerance 即可。把 token 粘进 JWT 解析工具核对 header 与声明,再用签名工具重算比对,两步就能定位是哪一类。