JWT 到底装了什么?贴一个 token 把 3 段拆开,实测 alg=none 的坑

举报
deli007 发表于 2026/10/14 23:03:41 2026/10/14
【摘要】 JWT 到底装了什么?贴一个 token 把 3 段拆开,实测 alg=none 的坑

一个 224 字符的 JWT 贴进去,页面当场把它拆成三段:27 字节的 header、107 字节的 payload、32 字节的签名。

填上密钥点一下按钮,页面自己算出的 HMAC-SHA256 和 token 里带的签名,32 个字节逐字节对得上。

图 1:三段拆解台,header / payload / signature 的 base64url 原串、填充符个数、解码字节数和十六进制签名都在同一屏

在线案例与源码:https://atomgit.com/deli007/demo_park/tree/main/codearts-jwt-decoder
生成物只有一个 index.html,纯前端、零依赖、不联网,token 和密钥都不出浏览器。

一、为什么值得拆开看:三段里藏着三个坑

JWT 天天在用,真正容易出事的地方只有三处:payload 是明文、过期时间没人查、算法名写在 token 自己身上。

我把这三件事都做成了能点、能改、能当场复现的演示。

二、准备环境:进入码道 Web,把需求写成一段话

浏览器打开 https://devcloud.cn-north-4.huaweicloud.com/chat/home ,在对话框里把要做的应用说清楚,就能拿到一个能跑的单文件应用。

本文这个解剖台就是这么来的,不用装软件,登录华为云账号即可使用。

三、JWT 不神秘,就是三块拼起来的一段字符串

JWT 是三个 base64url 串,中间用两个点号连起来。

第一段叫 header,说明用什么算法签名。第二段叫 payload,装业务声明。第三段是签名,前两段拼起来的哈希值。

我把示例 token 拆开量了一遍:三段分别是 36、143、43 个字符。

解码之后正好是 27、107、32 字节。

这个 4 比 3 的比例不是巧合,base64 用 4 个字符表示 3 个字节。

四、三段逐字节看,长度都是算得出来的

页面把每段的细节都摊开了,实测数字是这样:

段 解码后内容 base64url 长度 解码字节数 省略的填充符
header {"alg":"HS256","typ":"JWT"} 36 字符 27 字节 0 个
payload sub / name / iat / nbf / exp / admin 143 字符 107 字节 1 个
signature HMAC-SHA256 的二进制结果 43 字符 32 字节 1 个

base64url 和常见的 base64 差两处:+ 换成 -、/ 换成 _,末尾的 = 直接砍掉。

砍掉填充符之后长度不再被 4 整除,解码前得自己补回来。

页面里有一条断言专门盯这件事:单字节的 YQ 省略了 2 个填充符,补成 YQ== 才能解出 1 个字节。

header 恰好是 36 个字符,能被 4 整除,所以一个填充符都不用补。

五、验签要真算一遍,不是"看着像"

我去掉了所有"看起来对"的判断,只用浏览器自带的 Web Crypto API 算真签名。

密钥进 crypto.subtle.importKey,前两段拼起来送进 crypto.subtle.sign,出来 32 个字节。

拿这 32 个字节和 token 第三段解出来的 32 个字节并排对齐,逐字节比。

图 2:验签通过,本页计算和 token 携带的两行签名全绿,相同字节数 32 / 总字节数 32

这一步没有任何网络请求,密钥和 token 全程留在本地。

顺手量了一下开销:同样的验签跑 2000 次,总共 8.5 毫秒,平均一次 4.3 微秒。

解析 10000 次 header 加 payload 也只要 34.6 毫秒。

也就是说校验对接口延迟的影响可以忽略,该验的都得验。

六、改一个字节,签名立刻对不上

第三段是哈希,payload 动一个字符,哈希就全变。

我把示例里的 name 改成 Mallory、admin 改成 true,签名保持原样,再验一次。

页面算出的签名是 4122d31f88c33cc4…,token 里带的是 6ae58e1995d0340f…,32 个字节里没有一个相同。

图 3:篡改 payload 后验签失败,两行签名整段红底高亮,相同字节数 0 / 总字节数 32

红色高亮是我特意加的:只给一句"验签失败"没说服力,红色字节数会自己说明问题。

七、alg=none 才是真坑:算法写在 token 自己身上

签名的算法名写在 header 里,这个设计天生别扭。

攻击者把 header 改成 {"alg":"none"},第三段留空,payload 想写什么写什么。

验证方如果照着 header 里的 alg 去选算法,看到 none 就跳过验签,伪造的 token 直接过关。

正确做法是先定一份算法白名单,header 里的 alg 不在白名单里就拒。

页面把两条路都摆出来:白名单 {HS256} 命中不了 none,拒绝;信 header 的老写法,none 直接放行。

同一份输入、两种判断,一个拦得住,一个拦不住。

八、过期检查,别把 exp 当装饰

payload 里的 iat、nbf、exp 都是 Unix 秒,不换算根本看不出问题。

页面把它们换算成本地时间,再和当前时间比。

示例里的过期 token,exp 是 1791976160,换算过来是 2026 年 10 月 14 日 19 时 09 分。

图 4:过期 token 的时间卡片标红,明确写出已过期 3 小时 44 分;正常 token 写还有 4 天 23 小时有效

同一屏里还有一条正常 token,exp 写着还有 4 天 23 小时有效,用的是绿色。

只解码 payload 不判 exp,等于发一张永久通行证。

九、我踩的坑:第一版自检自己都不信

页面底部有 12 条断言,加载时自动跑,结果直接渲染出来,这是为了让我自己也没法糊弄。

第一版交上来,12 条里挂了 1 条。

挂的偏偏是最关键的第 6 条:正常 token 的 HMAC 验签。

原因是它把算出来的签名转成十六进制,再和 token 里 base64url 的签名串直接比,两个口径不同,永远不相等。

我用 Python 的 hmac 模块独立算了一遍,得到的 base64url 签名 auWOGZXQNA_1Nk9I4L6OnNHIokk39bMftkNHtp3oHsM 和 token 里带的完全一致,说明 token 本身没问题,是断言写错了。

改成同一个口径再比,12 条全过。

图 5:自检 12 条断言逐条列出实际值,全部通过

顺手还修了时间卡片里重复显示的一行状态,同一个"有效"印两遍,看着像有两条结论。

十、实测数据汇总

项目 实测值 说明
token 总长 224 字符 36 + 1 + 143 + 1 + 43
header 27 字节 解出 alg 与 typ
payload 107 字节 6 个声明字段
signature 32 字节 HMAC-SHA256 输出
HMAC 验签 2000 次 8.5 毫秒 平均 4.3 微秒一次
解码 + JSON 解析 10000 次 34.6 毫秒 平均 3.5 微秒一次
篡改 token 相同字节 0 / 32 整段签名失效
页面自检 12 / 12 通过 加载时自动执行

数字都在页面里能复现,刷新一次就重跑一遍。

十一、本地复现

生成物只有一个 index.html,纯前端、零依赖、不联网。

存到本地双击就能打开,file:// 下 Web Crypto 也能正常用。

想用静态服务也行:python -m http.server 8000,然后访问 http://localhost:8000 。

替换成自己的 token 时记得把密钥一起改掉,HS256 的签名是密钥相关的。

十二、用码道做这个小工具的体会

码道有三种使用方式:WebUI(浏览器对话)、TUI(终端命令行)和桌面 IDE(IDE 插件)。本文用 WebUI 版演示。

浏览器打开 https://devcloud.cn-north-4.huaweicloud.com/chat/home ,把需求写成一段话发出去,就能拿到一个能跑的 index.html。

这次最值钱的一条需求是"页面里要有自检脚本,把每条断言的结果渲染出来"。

正因为有这条,第一版那个十六进制比 base64url 的错才藏不住。

另一个体会是"要求展示实测值,而不是结论":验签结果旁边同时给出本页计算值和 token 携带值,读者一眼就能自己数相同字节数。

不足也说一句:这个工具只支持 HS256,RS256 这类非对称算法还没做;密钥是手填的,不能当生产环境的验签服务用。

它更适合当作理解 JWT 的解剖台,而不是替代后端的鉴权库。

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0)

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。