JWT 解析
解析 JWT 头部与载荷,并检查过期时间
JWT 解析与安全:能解开不等于可信
JWT 的结构
一个 JWT 是三个 Base64url 片段用点号连接:header.payload.signature。
- header:算法与类型,如
{"alg":"HS256","typ":"JWT"}。 - payload:声明(claims),也就是实际数据。
- signature:对前两段的签名,保证内容未被修改。
三段都用 Base64url 编码——字母表用 - 和 _ 替换 + 和 /,并且去掉末尾的填充 =。这也是为什么直接用标准 Base64 解码器处理 JWT 会失败。
必须建立的第一个认知:解码 ≠ 验证
payload 只是 Base64 编码,任何人都能读出内容,不需要密钥。把它丢进任何 JWT 解析器都能看到里面写了什么。
反过来更重要:看到熟悉的内容不等于这个 token 可信。真正决定可信度的是签名校验——而校验必须用服务端持有的密钥或公钥,在前端做不了。所以一个在线解析工具能做的是"让你看清里面写了什么",不是"告诉你这个 token 有效"。
这也是 JWT 的第一条使用纪律:不要在 payload 里放敏感信息。它和明文一样。JWE(加密的 JWT)才提供保密性,但那和常见的 JWS 是两套东西。
标准声明
| 字段 | 含义 | 常见坑 |
|---|---|---|
iss | 签发方 | 必须校验,否则任何有同名密钥的发行方都能冒用 |
sub | 主体(通常是用户 ID) | 不要直接当数据库主键用,可能需要映射 |
aud | 接收方 | 多服务共用一个密钥时必校验,防止 A 服务的 token 拿去调 B 服务 |
exp | 过期时间(Unix 秒) | 服务器时钟漂移会导致刚签发的 token 被判过期 |
nbf | 生效时间 | 存在时容易被忽略,导致提前使用被拒 |
iat | 签发时间 | 可用于拒绝"签发于很久以前"的长期 token |
jti | token 唯一 ID | 实现黑名单/撤销依赖它 |
时间字段全部是 Unix 秒,不是毫秒。写错单位会让 exp 变成公元几万年或 1970 年,这是排查时最常见的一类问题(解析器一般会把时间字段转成可读时间,直接看即可确认)。
算法选择与已知攻击
| alg | 类型 | 密钥形态 | 说明 |
|---|---|---|---|
| HS256 | 对称 HMAC | 共享密钥 | 快,但验证方也能签发 |
| RS256 | RSA 签名 | 私钥签、公钥验 | 多服务场景常用,公钥可公开 |
| ES256 | ECDSA 签名 | 私钥签、公钥验 | 密钥更短,签名也短 |
| none | 无签名 | — | 早期库接受它,等于完全无保护 |
几个真实存在过的漏洞模式:
- alg 混淆(RS256 → HS256)。服务端期望 RS256,用公钥验签;攻击者把 header 改成
HS256,然后用公开发布的公钥文本当作 HMAC 密钥签名。如果库里按 header 里的 alg 选择验证方式,就会用公钥去算 HMAC 并校验通过。防御方式:服务端固定期望的算法,绝不相信 token 自报的 alg。 - alg 置为 none。直接删掉签名段。现代库默认拒绝,但自研实现容易漏判——务必显式拒绝
none。 - HMAC 密钥过弱。HS256 的密钥如果是短口令或常见单词,可以被离线暴力破解——攻击者拿到任意一个合法 token 就能在本地反复试密钥,不需要碰你的服务端。密钥必须是长随机值。
- kid 注入。header 里的
kid若被拼进文件路径或 SQL 查询,可能变成路径穿越或注入点。
排查与调试流程
- 先看 header 的
alg,确认和服务端预期一致。 - 看
exp/nbf/iat,把时间字段换算成本地时间,与服务端时钟对比。 - 看
aud和iss是否是本服务。 - 确认 token 是从哪拿到的:如果是
localStorage,任何 XSS 都能读走它;放在HttpOnlyCookie 里读不到,但要处理 CSRF。 - 最后才怀疑签名——签名错误通常意味着密钥配置不一致(比如多实例读到了不同的密钥文件)。
本工具只做本地解码与结构展示,不发送 token,不做签名校验(那需要密钥,不该交给第三方网站)。涉及生产环境的 token,请勿粘贴到任何在线工具,包括这个——用你本地的调试手段更稳妥。
常见问题
为什么不能验证签名?
签名验证需要服务端密钥(HS256)或公钥(RS256)配合验签逻辑。前端解析只用于查看内容。
Token 会泄露吗?
不会,解析完全在浏览器本地完成。但仍建议不要把线上真实 token 贴到任何网站,包括本站。