技术笔记
JWT 的三段分别是什么,校验时在看哪里
解码能看到头和载荷,不代表这枚令牌可以被信任。这篇分开讲 Base64URL、签名和声明检查,并说明为什么 alg 与密钥必须一起看。
作者 Brook/更新于 2026-10-09/约 12 分钟
它只是三段用点分开的文本
一枚 JWT 的样子是 header.payload.signature。三段都是文本,用 . 连起来,没有加密外壳。拿到字符串的人不用密钥也能读前两段。密钥的作用是让第三段对前两段做校验:改一个字,签名就对不上。
所以「能解码」和「能信任」是两件事。调试时解码很有用,可以看到签发者放了哪些声明;上线时只解码就放行,等于允许任何人自己写一段头和载荷。浏览器里的解码工具适合检查内容,不适合充当鉴权。
头和载荷是 Base64URL,不是加密
- 01切分按点拆成三段少一段或多一段都不是一枚完整 JWT。
- 02头Base64URL 解码得到 JSON,里面有 alg 和 typ。
- 03载荷Base64URL 解码得到声明:sub、exp、自定义字段。
- 04签名先不要解码成 JSON它是对前两段原文算出的摘要。
这里用的是 Base64URL:+ 换成 -,/ 换成 _,填充用的 = 常常被去掉。用普通 Base64 解码器直接贴,可能会在这两个字符上报错,或者因为缺填充而失败。把 - _ 换回去、按长度补 =,才是同一份数据。头和载荷解码后应该是 JSON 对象,不是任意文本。
头里最重要的字段是 alg。它告诉校验方用哪一种算法。HS256 是对称的 HMAC,签发和校验用同一把密钥;RS256、ES256 是非对称的,校验用公钥,签发用私钥。typ 通常是 JWT,它只是个提示,不能代替算法检查。
签名在证明什么
签名的输入是「头的 Base64URL 文本 + 点 + 载荷的 Base64URL 文本」,也就是令牌里点号之前的那一整段原文,不是解码后的 JSON 再序列化一次。校验方必须用收到的原始字符串计算。如果先解码再自己 JSON.stringify,键序或空白一变,摘要就变了,本来合法的令牌会被判失败。
HMAC 把这串原文和密钥一起送进哈希。非对称算法则用私钥对摘要签名,校验时用公钥验证。两种情况下,签名证明的都是「持有密钥的人认可这段头和载荷」。它不证明载荷里的话是真的,也不加密载荷。把用户角色写进载荷,只是签发方的声明,资源服务器仍然要自己判断这个声明能不能信。
校验时实际按这个顺序看
- 01声明看 exp、nbf、iss、aud签名正确但过期,仍然要拒绝。时钟偏差只允许很小的窗口。
- 02签名用约定的密钥验证第三段比较要在常数时间里做,避免用提前返回的字符串比较泄露差异位置。
- 03算法alg 必须落在允许列表里不要照着令牌自己说的算法去选密钥。允许列表在服务端写死。
- 04结构三段、Base64URL、JSON结构不完整就不用做密码学运算。
exp 是过期时间,nbf 是生效时间,都是 Unix 秒。只检查签名、不检查时间,等于接受一张已经作废的旧票。iss 和 aud 用来确认这枚令牌是发给本服务的,而不是从另一个系统抄过来的。自定义声明可以有,但标准声明的含义不要重定义。
alg 与密钥必须一起决定
历史上有过一类错误:校验方读取令牌头里的 alg,如果对方写成 none 就跳过签名。攻击者只需要自己组一段头和载荷,签名留空。正确的做法是服务端先决定「我只接受 HS256」或「我只接受 RS256」,令牌说了别的就拒绝。none 只应该出现在明确的测试配置里,生产配置里没有它。
另一类错误是算法混用。服务端拿 RSA 公钥的 PEM 文本去当 HMAC 的密钥,同时又允许令牌自己选择 HS256。攻击者用公钥(本来就是公开的)作为 HMAC 密钥签一枚 HS256 令牌,校验方会算成通过。允许列表加上「这种算法只用这把密钥」才能堵住。公钥和对称密钥不要进同一个查找表。
解码放在浏览器,校验放在服务端
在页面里解码,适合看「这枚令牌里到底写了什么」。密钥不应该放进前端去完成最终校验。对称密钥一进浏览器就等于公开;用公钥在前端做一次验证,也挡不住用户改自己的页面跳过它。前端最多用来提前发现明显过期或格式不对,真正放行请求的那一次校验要在持有密钥的服务端做。
本站的 JWT 工具只做解码和结构展示:拆三段、按 Base64URL 还原头和载荷、标出声明。它不保存密钥,也不把令牌发到服务器。要用它核对签发结果,可以;要用它代替服务端鉴权,不行。