JWT 仅仅是 Base64 加了件夹克——在线几秒钟即可解码
JWT令牌看起来很复杂,但实际上主要是Base64编码。学习其三部分结构、如何立即解码声明、开发人员常遇到的陷阱(算法混淆、有效载荷密钥、过期问题),以及在什么情况下应使用JWT而非会话令牌。
你正在查看一个网络标签页。有一个请求头写着 Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c 你的第一反应是:“这到底是什么?”
好消息:这个字符串的大部分只是Base64编码。那个看起来很吓人的数据块其实只是穿着大衣,假装更神秘而已。让我们把它脱掉。
JWT 实际上是什么
JSON Web Token 是三个用 Base64url 编码的片段,用点号连接在一起的:
- 标头 – 算法和令牌类型(例如
{"alg":"HS256","typ":"JWT"}) - 有效载荷 – 声明:用户ID、角色、过期时间,服务器决定打包进去的任何内容
- 签名 – 实际上强制执行安全性的部分
头部和有效载荷是 未加密的。它们是 Base64url 编码的,这意味着任何拥有令牌的人都可以读取它们——无需密钥。只有签名部分能防止篡改:有效载荷中的任意一个字符被更改都会使令牌无效。
要立即解码有效载荷:获取第二个片段(两个点号之间的部分),将其放入一个 Base64 解码器中,你就会看到明文 JSON。这就是全部了。这就是这个魔术。
如何在几秒钟内解码 JWT
最快的方法:将你的令牌粘贴到 JWT 解码器 中,IOTools 提供。它会将三个部分分开,解码每个部分,并以格式化的 JSON 显示头部和有效载荷——无需账户、无需设置、无需“登录以解锁解码器”。
你在典型有效载荷中会立即看到的内容:
sub– 主体(通常是用户ID)iat– 发行时间戳(Unix 时间戳)exp– 过期时间戳iss– 发行者- API 添加的任何自定义声明:角色、权限、计划等级等。
如果你只是想知道令牌是否已过期,而不想搭建完整的解码器,那么 JWT 过期检查器 会读取 exp 声明,并告诉你还剩多少时间——或者它已经过期的时间。
JWT 使用中的三个常见陷阱
1. 你忘记检查的过期时间
这 exp 该字段只是有效载荷中的一个数字——服务器本应验证它,但许多旧代码库都没有验证,或者在时区处理上存在错误。如果用户莫名其妙被登出(或莫名其妙一直保持登录状态),请解码令牌并查看 exp 与当前 Unix 时间戳。该 过期检查器 可以一键完成此操作。
2. 算法混淆
头部的 alg 字段告诉验证器使用哪个算法。一些较旧的 JWT 库会接受 {"alg":"none"} 并完全跳过验证——移除签名并认为任何有效载荷都是有效的。这是一种已知的攻击。始终验证你的库是否强制使用特定算法白名单,并拒绝 none.
另一种情况:RS256(非对称)与 HS256(对称)。如果攻击者知道你的公钥,他们可以通过将头部改为 HS256 并使用公钥作为密钥来伪造令牌——如果库天真地信任头部的 alg 声明。解决方法:在库中显式配置算法,而不是“由令牌决定”。
3. 有效载荷中的密钥
因为有效载荷是 Base64 编码而非加密,你放入其中的任何内容都可以被客户端读取(以及任何拦截令牌的第三方,尽管你处处使用 HTTPS,对吧?)。不要在 JWT 声明中放入密码、个人身份信息(PII)或内部系统细节。有效载荷仅用于授权元数据——而不是存储敏感数据的地方。
从你自己的应用中解码一个令牌,看看里面有什么。你可能会惊讶于多年前的某个开发者塞进去的内容。
JWT 与会话令牌:实际区别是什么
持久的争论。以下是直接对比:
| JWT | 会话令牌 | |
|---|---|---|
| 状态存储位置 | 在令牌内部(无状态) | 服务器端(数据库或内存) |
| 撤销 | 困难——令牌在 exp 过期前有效 | 除非你维护一个黑名单 |
| 可扩展性 | 简单——只需删除会话记录 | 适合分布式系统;无需共享会话存储 |
| 需要粘性会话或共享存储(如 Redis 等) | 有效载荷可见性 | 客户端可以读取声明(未加密) |
| 对客户端是不可见的 | 存储风险 | localStorage 易受 XSS 攻击;httpOnly cookie 更安全 |
| httpOnly cookie(标准做法) | 令牌大小 | 较大——所有声明都内嵌在令牌中 |
| 较小——只是一个 ID | 最佳用途 | API、微服务、跨域认证 |
传统服务器渲染的网页应用
在真实工作流中调试 JWT
当使用 JWT 的 API 出现问题时,通常的流程如下:
- 复制令牌 从失败的请求中(授权头、查询参数或 Cookie —— 你的 API 放置的位置)
- 粘贴到 JWT 解码器 以查看原始声明
- 确认
exp——令牌是否已过期?使用 过期检查器 如果你不想在脑海中进行 Unix 时间戳计算 - 确认
iss且aud——发行者和受众是否与你的服务期望匹配? - 检查头部的算法 ——是否与你的服务器配置匹配?
- 检查签名格式 ——三个部分?两个?被篡改的令牌有时会显示为两个部分且没有签名
大多数 JWT 调试在第3步就终止了。令牌被颁发时具有较短的TTL,被缓存到某处,最终到达时已过期。这种情况很常见且无聊。
签名:唯一真正重要的部分
签名是通过以下方式计算的: HMACSHA256(base64url(header) + "." + base64url(payload), secret)。服务器在颁发令牌时生成它。在验证时,它重新计算相同的哈希值并进行比较。如果有效载荷有任何修改——哪怕是一个字符——哈希值将不匹配,令牌将被拒绝。
这就是为什么解码 JWT 以查看声明是安全且简单的,但没有密钥伪造一个令牌是不可能的。Base64 是外衣,签名是门锁。
对于非对称算法(RS256、ES256),签名密钥是服务器上的私钥,永远不会离开认证服务器。验证密钥是公钥,任何服务都可以使用——无需共享密钥。这是微服务架构的正确设计,多个服务需要验证令牌,但只有一个是令牌的颁发者。
