直接给结论:哪里都别贴。JWT 是一张能直接用的通行证,而不是通行证的说明。 收到它的服务器在它过期之前通常就能以你的身份行事 —— 而你完全无法知道它有没有被记录、被存下来、被重放。 在本地解码吧:它的载荷只是 Base64,浏览器自己就能读,什么都不用发出去。
搜「jwt 解码」,排在前面的全是让你把 token 粘进输入框的网站。它们都能用。问题恰恰在这里。
一个 JSON Web Token 不是登录状态的「描述」,它就是登录本身。签发它的服务器会直接把它当作身份证明接受, 不需要任何额外校验,直到它过期为止。所以把 token 贴进第三方网站,更接近于把你的会话交出去, 而不是「查一下里面写了什么」。
那个网站实际收到了什么
网页中间那个输入框不是本地函数,它是一个表单。你按下解码按钮,token 就被发到了服务器上 —— 通常是走 HTTPS,通常那台服务器属于一个你从没听说过的运营者,而且你无从得知它之后会被怎么处理。
那台服务器能看到完整的 token,也就是说它能看到:
- 载荷里的每一项声明 —— 用户 ID、邮箱、角色、租户、权限,以及应用往里塞的任何其他东西。
- 精确的过期时间,所以它清楚这张通行证还能用多久。
- 是哪个签发方签的,就在
iss声明里 —— 这等于告诉了对方你在做的是哪一类系统。 - 签名本身,而对很多服务来说,这就足够从另一台机器上把这个 token 重放出去。
一个有恶意的人甚至不需要动什么脑筋,把请求体记下来就够了。而到达免费解码服务的 token, 按定义就是刚刚在真实会话里用过的 token;这些网站的流量是免费且持续的。
「我们不记录日志」是一句无法验证的话
有些这样的站会说它们不记录请求。这可能是真的,运营者也可能确实诚实。但你从外部没有任何办法核实, 而猜错的代价是不对称的:猜对,只省下你本地解码那三十秒;猜错,意味着有人拿着你应用的一张有效通行证。
还有一种更小、但更可能发生的失败:出于性能考虑缓存了最近解码过的 token 的托管服务; 页面上的第三方统计脚本顺手抓走了表单字段;或者六个月后这家服务被攻破,历史请求日志被整个拖走。 这些都不需要任何恶意。
解码 JWT 简单到可笑,这才是重点
一个 JWT 由点号隔开的三段组成:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9 . eyJzdWIiOiIxMjMiLCJuYW1lIjo... . dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk
前两段是 Base64URL 编码的 JSON。解它们就是一个毫秒级的字符串操作,完全不涉及密码学:
header = JSON.parse(base64url_decode(parts[0]))
payload = JSON.parse(base64url_decode(parts[1]))
signature = parts[2] // 不可解码的字节,保持原样
整个算法就这么点东西。这里没有任何一步需要服务器 —— 那么为它把一张有效通行证送上互联网,就很难说得过去。
你其实可以在浏览器控制台里自己验证,只是因为 URL 安全字符集和缺失的补位符,手写起来有点烦。 这里的本地 JWT 解码器做的是同一件事,顺带把时间戳转成了可读日期; 而这个页面是一个纯静态文件,没有任何网络请求 —— 你可以在开发者工具的 Network 面板里确认这一点。
「解码」不等于「验证」,而很多网站把这两件事混着讲
几乎所有在线解码器都会给你看一份解出来的载荷,然后让你以为这个 token 是有效的。它什么都没校验过。 解码只能证明这段 Base64 里面是格式正确的 JSON。
验证签名需要签名密钥;如果 token 用的是 RS256 这类非对称算法,则需要公钥。 这本质上是一个服务端操作 —— 签名的全部意义就在于:手里没有密钥的一方造不出签名。 一个网页无论界面暗示什么,都没法替你做验证。
所以对任何解码器(无论托管还是本地),诚实的描述都是同一句: 它展示的是这个 token 声称了什么,而不是它是否可信。把载荷当成一份「声明」看待,而不是证据。
这个错误下面还压着另一个错误
如果你是因为开发时要看看 token 里有什么才去找解码器,那你手上这个 token 大概就是真实会话里的真货。 这才是真正值得修的问题。
- 开发环境用短过期时间。一个十五分钟就失效的 token,能把任何泄露的损失限制在一个小范围里 —— 无论泄露是通过解码器、截图还是一段粘贴出来的日志。
- 要知道
exp是服务器强制的,不是客户端。浏览器可以选择不用过期的 token, 但只有服务器能拒绝它。 - 吊销比过期更重要。如果 token 已经暴露,就吊销那个会话,别等时钟走完。 轮换签名密钥可以一次性废掉所有 token,下手重,但干净利落。
- 记住它是持有者凭据。谁拿着它,谁就被当成那个用户。token 本身里面没有第二道验证。
那应该怎么做
- 在本地解码。任何不发起网络请求的浏览器端解码器都行;本站的这个在这里。
- 如果非要用托管服务,就用一张你已经吊销过的 token,或者测试环境里的一次性 token。
- 永远不要把生产环境的 token 贴进任何不是你写的东西里。
这和在浏览器里算密码哈希、或者 解码 Base64 是同一个道理:输入本身就是秘密, 在本地运行这些操作不是一个偏好问题,而是整件事的意义所在。 一个处理凭据的工具如果把它们发到别处去,它从那一刻就已经失职了。