为什么 JWT 经常被“看起来正确”
JWT 的 Header 和 Payload 使用 Base64URL 编码,复制到任何解码工具里都能立刻看到 JSON。这种可视性容易让人产生一个危险直觉:只要内容读得出来,Token 似乎就是合法的。实际上,编码只是为了让数据适合放进 URL、请求头或表单,它与签名、加密和授权完全是不同层次的问题。
调试 JWT 时,最重要的是先分清两件事:你能看到什么,以及服务端最终验证了什么。解码工具能帮助你确认字段和时间,但不能替你证明签名正确,也不能判断账号是否被禁用、Token 是否被撤销。
JWT 的三段结构与 Base64URL
一个典型 JWT 使用点号分成三段。第一段是 Header,描述算法和类型;第二段是 Payload,承载 iss、sub、aud、exp 等声明;第三段是 Signature,用于服务端验证前两段是否被修改。Base64URL 在标准 Base64 基础上把 + 替换为 -,把 / 替换为 _,并去掉末尾的等号,方便安全地放入 URL。
解码时不能简单地把 Base64URL 当成普通文本。正确做法是恢复填充字符,再用 UTF-8 解码字节。如果只使用支持 ASCII 的旧方法,遇到中文声明时可能出现乱码。本站 JWT 解码工具会在浏览器本地完成这一过程,并把 Header 和 Payload 分开显示。
解码与验签的关键区别
| 动作 | 需要的条件 | 能回答的问题 | 不能回答的问题 |
|---|---|---|---|
| 解码 | 只有 Token 文本 | Payload 里写了什么 | Token 是否可信 |
| 验签 | 密钥或公钥、指定算法 | 内容是否被篡改 | 账号是否有权限 |
| 鉴权 | 验签结果、用户状态、权限数据 | 当前请求是否允许执行 | 业务规则是否正确 |
一个常见错误是前端解码后发现 role: "admin",就认为用户可以进入管理员页面。攻击者完全可以修改 Payload,再重新编码生成一个新的 Token;如果没有验签,修改后的内容看起来同样整齐。真正的权限判断必须在服务端依据可信身份完成。
时间字段:exp、iat 和 nbf
JWT 标准时间字段使用 Unix 秒级时间戳。exp 表示过期时间,iat 表示签发时间,nbf 表示生效时间。排查 401 时,先看 exp 是否早已过去,再确认服务器时间是否与认证服务同步。有些系统允许几十秒的时钟偏差,有些则要求严格一致,不能只凭客户端时间下结论。
如果 Token 在刚签发后立刻失效,检查签发机和验证机是否使用同一时区基准,以及容器是否在启动时读取了错误时间。还有一个常见问题是把毫秒时间戳写进 exp。标准字段期望秒,毫秒值会被解释成极遥远的未来,表面上永远不会过期,实际却可能触发其他校验失败。
算法与密钥管理
Header 中的 alg 告诉验证方应该使用什么算法,但 Header 本身也来自请求,不能不加判断地直接信任。服务端应固定允许的算法集合,并把密钥与算法绑定。例如 HS256 使用共享密钥,RS256 使用私钥签名、公钥验证,算法混用或把公钥当共享密钥使用,都可能造成严重安全问题。
密钥轮换时,Header 中的 kid 可以帮助验证方选择正确密钥。服务端不仅要找到匹配的公钥,还要拒绝未知 kid、过期密钥和不符合预期签发者的 Token。公钥可以通过 JWKS 端点发布,但缓存时间、网络失败和密钥更新策略都要设计清楚,否则会出现“认证服务正常、业务服务却全部 401”的联调问题。
常见排查场景
本地请求正常,部署后全部 401
优先检查生产环境和测试环境是否使用了不同的签发者、受众或密钥。很多人把测试 Token 复制到生产接口,Payload 看起来没有明显异常,但 aud 或 iss 根本不匹配。解码只能看到字段存在,不能证明这些字段属于当前环境。
用户刚登录就提示 Token 过期
检查认证服务签发时的 exp 单位、服务器时钟、网关是否缓存了旧响应,以及浏览器或客户端是否在请求头中带上了旧 Token。不要通过延长过期时间来掩盖问题,先找到过期字段为何与预期不一致。
Token 可以解码,但签名验证失败
确认签名时使用的是原始三段字符串,而不是重新格式化后的 JSON。JSON 的空格、字段顺序和 Unicode 转义都会改变签名输入。复制 Token 时出现的换行、空格或 URL 编码也可能导致签名验证失败。
浏览器端应该怎样保存 Token
没有一种存储方式适合所有系统。HttpOnly、Secure、SameSite Cookie 能减少脚本直接读取 Token 的风险,但要设计 CSRF 防护;把 Token 放在内存中可降低持久化泄露风险,却会在刷新后丢失;localStorage 方便读取,却更容易受到 XSS 影响。选择方案时应结合攻击模型、前后端架构和用户场景,而不是只看某个博客给出的单一结论。
无论使用哪种方式,都不应把长期密钥、刷新令牌和普通业务数据混在同一个位置。刷新令牌需要更严格的生命周期和撤销策略,访问令牌应尽量短,服务端应记录必要的安全事件,并通过密钥轮换降低单次泄露的影响。
一份可执行的调试清单
- 确认 Token 有三段且没有被截断、换行或加入引号。
- 解码 Header,核对 alg、typ、kid。
- 解码 Payload,核对 iss、sub、aud、scope 和时间字段。
- 在服务端验证签名、时间、受众、签发者和撤销状态。
- 查看服务端日志中的拒绝原因,而不是只根据前端解码结果猜测。
- 如果涉及生产凭据,使用受控测试 Token,避免把真实 Token 发到第三方网站或聊天工具。