去年春天,用户开始从 toolz。dev。not 有时 - 不断。登录,单击一个工具,boom:返回登录屏幕。后端发出 15 分钟访问令牌和 7 天刷新令牌,I'd 测试了那条流一百次。所以我自然地假设刷新端点被打破,花了一个小时四十分钟阅读了与之没有问题的 Express 中间件。
然后我终于做了显而易见的事情,我从授权标头中抓取了一个实时访问令牌,粘贴到解码器中,然后查看了声明。 exp 没事。的 iat was fine。token 又有效了 14 分钟,这意味着服务器没问题 - 而且 bug 必须放在客户端上,果然:我的前端在检查 payload.exp < Date.now()。 exp 是自纪元以来的秒。 Date.now() is 毫秒。每一个刚铸造的代币看起来像是在 1970 年左右的某个地方过期的,所以客户端"有帮助"在服务器还没有获得发言权之前就把所有人注销了。修复的三个字符- /1000- 经过近两个小时的狩猎。
That's 在你的工具箱里有一个JWT解码器的整个音调 一个JWT看起来像线噪声- 三块base64url胡言乱语用点粘在一起- 但它's只是穿着风衣的JSON。在你能读取声明的那一刻,你的一半的auth错误不再是谜。错误的受众,过期的令牌,缺失的角色,时钟偏差,毫秒vs秒- 他们're一旦你解码,都以纯文本坐在那里。
但是 - 这很重要 - 你解码不是一个中立的选择真正的访问令牌是一个实时凭据,将其粘贴到一个解码器站点,该解码器站点将令牌发送到服务器,而你've刚刚将一个工作密钥存入你的API到一个陌生人's请求日志。那个's我构建的具体原因 Toolz.dev JWT 解码器 要完全在浏览器中运行。下面将详细介绍这一点。
TL;博士: 要在线解码 JWT,请将其粘贴到 Toolz.dev JWT 解码器- 它会立即分割标头、有效负载和签名,翻译
exp/iatinto human date,并且运行100%客户端所以令牌永远不会离开你的机器,烧入内存的一件事:解码不是验证,一个JWT只是任何人都可以读取的base64url编码的JSON-只有带有密钥的签名验证才能证明's值得信赖。
主要特点
即时标头、有效负载和签名分割
Paste一个令牌,解码器立即将其分成三个部分:头部(算法和令牌类型),有效负载(你的声明)和签名(左编码,因为它's一个原始MAC或签名-那里's那里没有人类可读的东西)。没有提交按钮,没有页面重新加载。这在验证之前准确地反映了你的auth库内部所做的事情:split on .,base64url-解码前两段,解析为json,看到部分并排布局是最快的格式直观构建方法,几十个令牌后,你'll开始识别一个rs256 Auth0令牌对一个HS256 Laravel令牌一眼-头部每次都送。
人类可读 exp、iat 和 nbf 时间戳
最有用的功能是句号。 exp、 iat、和 nbf 是numericdate值- 自Unix时代以来的几秒钟- 并且没有人,包括我自己,可以读取 1783430700 and tell you whether's 下周二还是宇宙的热死,解码器将每个时间戳的声明转换为实际的日期和时间,在您本地时区和UTC。这是经典的毫秒vs秒bug瞬间可见的地方:如果您的解码 exp 56000 年代的渲染作为日期- 某人塞了一个 JavaScript Date.now() into 一个期待秒的领域 I've 运送了那个 bug 看到荒诞的日期就是诊断 对于更深层次的时间戳考古学, the 时间戳转换器 距离酒店仅一页。
到期倒计时和状态
除了仅渲染日期之外,解码器还会告诉您令牌's 当前状态:有效、过期或尚未激活(何时) nbf is in the future)。如果令牌's还活着,你就会得到一个倒计时到期。这听起来像是一个小小的方便,直到你're调试一个间歇性的401,需要回答"这个特定的令牌在请求被解雇时是否已经死亡?"一遍又一遍地与你的服务器进行倒计时比较's配置的TTL也很快就会捕获错误配置-如果你的访问令牌应该活15分钟,倒计时说6天,那么你的发行代码正在读取错误的配置值。
算法和标头检查
解码的标头向您显示 alg 並 typ (加x) (加x) (加x) (加x) (加x) (加x) (加x) (加x) (加x) (加x) (加x) (加x) (加x) (加x) (加x) (加x) (加x) (加x) (加x) (加x) (加x) (加x) kid 和朋友在场时),它回答了对于安全性很重要的问题,而不仅仅是调试。这是令牌HS256还是RS256?是 kid 匹配您的 JWKS 端点实际服务的密钥?其中一个大:是 alg 它不应该是这样的东西 none?代币声称 "alg": "none" 是测试夹具或有人探测你的验证者- 无论哪种方式,你都想立即看到它我首先检查标题在每个陌生的令牌上,在我阅读单个声明之前。
语法突出显示、格式化的 JSON 声明
Raw解码的有效负载是单行JSON blobs,身份提供者喜欢打包它们:嵌套对象,命名空间自定义声明,范围数组解码器漂亮地打印所有内容,语法突出显示所以 roles、 scope、 aud arrays,而嵌套的权限对象其实是可以扫描的,它's同样的处理the json格式化程序 gives任意json,自动应用于您的索赔。当您're比较两个代币时- 比如说,一个来自可以访问端点的用户,一个来自可以't-格式化的输出将眯眼练习变成十秒的差异。
100% 客户端 - 您的令牌永远不会离开浏览器
I'd争抢的功能就是这个。粘贴的访问令牌不是样本数据- it's一个实时凭据,它作为真实用户进行身份验证,直到 exp。任何将你的令牌发布到后端的解码器都刚刚将工作密钥写入服务器日志,分析,也许是第三方错误跟踪器。toolz。dev解码器在javascript中执行所有解码,在你的选项卡中没有传输任何内容,没有存储任何内容。don't接受我的话:打开devtools,观看网络选项卡,粘贴一个令牌。零请求。我写下了为什么这个架构对每个敏感输入工具都很重要 我关于在线工具中的数据隐私的文章。
可与任何 JWT、任何堆栈配合使用
JWTs是一个标准- RFC 7519- 所以解码器不't关心谁铸造了你的。Auth0和Firebase令牌及其命名间隔自定义声明,Laravel Sanctum相邻设置,Keycloak,Supabase,AWS Cognito,或者手卷HS256令牌我自己的Express后端标志为toolz。dev - 如果它's三个base64url段由点连接,它解码。那包括格式错误的几乎JWT:如果第二段获胜't解析为JSON,解码器告诉你哪个部分被破坏而不是默默失败,这本身就是诊断。复制截断令牌比你更常见'd认为。
如何使用 JWT 解码器
1步:抢到令牌
在应用程序保留的地方查找令牌。最常见的是:DevTools → 网络选项卡 → 单击请求 → 复制 Authorization: Bearer eyJ... header值(没有单词"bearer")。或者检查应用程序→本地存储/cookies,因为很多应用程序都在那里存储令牌。在后端,记录它或从测试套件中提取。复制整个字符串 - 一个JWT,它失去了最后几个字符仍然解码,但永远不会验证,而那个's一个令人困惑的小时你不需要't。
第 2 步:将其粘贴到
打开 jwt解码器 and paste。re 解码发生时,你键入 - 没有按钮。如果你're 紧张地粘贴生产令牌的任何地方(良好的本能),首先打开网络选项卡,并确认没有传输任何东西。它是't。那个偏执检查需要十秒钟,它's 恰好是I'd 在别人身上做的事情's 工具。
第三步:阅读三个部分
标题第一:确认 alg 是你的系统所期望的和 typ 於 JWT。然后有效载荷: iss (是谁铸造的), aud (它是谁's为), sub (哪个用户),加上你的堆栈添加的任何角色、范围或自定义声明。签名保持编码 - it's加密输出,而不是数据。如果标头说 none、停下来检查一下验证器's 允许列表之前的任何其他内容。
第 4 步:检查 exp 和咬人的索赔
看看解码后的 exp date和到期状态。expired?there's你的401。有效但反正被拒绝了?现在比较 aud 並 iss 反对您的验证者's配置 - 不匹配是到期后第二常见的原因。如果任何时间戳渲染为五位数年份,恭喜:you've 发现了一个毫秒与秒的错误,特此欢迎您来到一个非常大的俱乐部。
JWT内部实际上是什么's?三部分的解剖
RFC 7519 中定义的 JSON Web 令牌是三个由周期连接的 base64url 编码段: header.payload.signature。(严格来说,签名品种是每个RFC 7515的JWS - 那里's一个加密表弟,JWE,但几乎每个令牌你'将在野外见面都是一个签名JWS。)
关键词是 编码。Base64url是一种传输编码- 一种可逆的方式,可以使字节URL安全- 而不是加密,任何持有JWT的人都可以读取头部中的所有内容,并使用零密钥,零秘密,零努力来玩原始编码中的 Base64 转换器 而你'将看到它'的标准字母表 + 並 / 换成 - 並 _的, 填充掉了, 我写了更多关于编码本身在 Base64 编码指南。
解码典型的标头,您会得到:
{ "alg": "HS256", "typ": "JWT" }
根据注册权利要求 RFC 7519 构建的有效负载定义:
{
"iss": "https://toolz.dev",
"sub": "user_8f3a2c",
"aud": "toolz-api",
"exp": 1783431600,
"nbf": 1783430700,
"iat": 1783430700,
"jti": "b4d1f0e2"
}
iss 是发行人, sub 题(通常是您的用户 ID), aud 目标观众, jti 唯一的令牌 ID。 exp、 nbf、和 iat 是 NumericDate 值: 秒 unix时代以来。不是毫秒。javascript's Date.now() 返回毫秒,混淆两者会产生立即过期的令牌(我的 toolz。dev 注销错误)或令牌 exp 56,000年的日期,实际上永远不会过期 - 这是悄悄的更危险的失败。
HS256 与 RS256。 HS256通过共享秘密与HMAC签署 - 快速,简单,但验证令牌的每项服务也都持有秘密,任何持有秘密的人都可以 薄荷 tokens。fine像我的后台这样的巨石,其中发行者和验证者是相同的过程。rs256用私钥签名,并用公钥验证,因此您可以发布公钥(通过jwks),让十几个微服务验证,而它们中的任何一个都无法伪造分布式系统和第三方idp应该在rs256或其ecdsa/eddsa兄弟姐妹上。
的 alg: none 攻击。 RFC 7519 允许在以下情况下使用不安全的 JWT alg 於 none 且签名为空,早期库信任标头's alg 盲目地,因此攻击者剥离了签名,设置 alg 到 none,并带着完全攻击者控制的声明航行通过验证。一个相关的技巧将 RS256 交换为 HS256,因此验证者使用 公共 key 作为 HMAC 秘密,这就是为什么 RFC 8725 - JSON Web 令牌最佳当前实践 - 是直率的:验证者必须将其允许的算法固定在代码中,并且永远不要让令牌选择。如果您的库调用不't 包括一个显式 algorithms 列出,今天解决这个问题。
解码与验证--重要的线路。 Decoding是读取;验证是信任,在代码上的区别:
// Decoding: no secret, no trust. Anyone can do this.
const payload = JSON.parse(
Buffer.from(token.split('.')[1], 'base64url').toString()
);
// Verifying: proves the signature AND pins the algorithm.
const verified = jwt.verify(token, secret, { algorithms: ['HS256'] });
在线解码器做第一件事。它可以向您展示声明;它不能--也不应该假装--告诉您代币是真实的。只有 verify借助密钥,可以做到这一点。永远不要对解码但未经验证的声明做出授权决定。
这导致了最后一条规则: 切勿将秘密放入 JWT 有效负载中。 无密码、无 API 密钥、无数据 you'd 介意攻击者读取。有效负载是公共的,由结构 - 签名反对篡改,对读取开放。如果它's在令牌中,假设整个互联网都可以看到它。
常见用例
API 开发过程中调试 401
401是HTTP中信息量最少的状态代码,服务器说不- 但令牌是否过期了 错误的受众?用陈旧的密钥签名?完全因为你的拦截器没有't开火而错过?从失败的请求中解码实际令牌,在几秒钟内崩溃了搜索空间的一半时间 exp 独自回答。另一半,比较 iss 並 aud 针对您的验证器's环境配置发现一个开发令牌被重播反对分期,反之亦然。我将解码器固定在我的 HTTP 客户端旁边,以完成此循环;它's是我的核心条目 API调试工具包。先解码,再读中间件- 反向订单花了我一个小时四十分钟一次,我打算继续收集那节课的兴趣。
解释惊喜注销
当用户报告"它不断地让我退出时,"令牌'的时间戳是你的证人陈述。解码一个新的访问令牌并检查之间的间隙 iat 並 exp-实际上是你配置的15分钟,还是env var覆盖到60秒?然后检查刷新令牌's 7天窗口是否是你认为的时钟偏移也出现在这里:如果你的发行服务器's时钟运行几分钟快,令牌已经由客户端到达古老's计算。当然,秒-vs-毫秒比较错误-那个比特工具z。dev-宣布自己当你看到一个完全有效的时刻 exp 在代币上,您的客户发誓已过期。
审核您的身份提供商在代币中的投入
大多数团队从未真正阅读过他们的 IdP 铸造的代币,值得做的 ' 解码一个,您可能会发现电子邮件地址、全名、图片 URL、租户标识符 - PII 在每个 API 请求中都随身携带,存储在本地存储中,并且任何掌握该代币的东西都可以读取。这里'我固执己见的立场,而 I'll 与任何人争论:don't 将用户电子邮件放入 JWT 有效负载中。 sub claim 恰恰存在,因此您可以携带不透明的标识符并查找服务器端的人类详细信息,每一项额外的 claim 都是您're 广播的数据和您're 在每项请求上付费的字节解码、审核,然后去修剪您的 IdP's 索赔映射。
验证授权调试期间的角色和范围
Authentication 说你是谁;授权说你能做什么 - 当授权行为不当时,答案就在声明中。用户发誓他们'是一个管理员,但得到403?解码他们的令牌。如果 role 说 user,代币是在推广前铸造的,他们需要重新登录- 无状态代币携带陈旧快照的经典后果,如果角色存在但访问仍然失败,请检查确切的声明名称和形状: roles vs。 role,数组vs字符串, scope 作为空间分隔字符串 vs scp 作为数组。中间件检查 payload.roles.includes('admin') 反对有效负载 role: "admin" 默(silently)和令人愤怒地失败,两个并排解码的代币- 一个工作,一个不工作- 通常在一分钟内结算。
比较访问和刷新令牌内容
Mine 这样的双代币设置中,这两个代币应该看起来有意义的不同,而解码两者都是审计的访问代币:短 exp另外,无论 API 根据请求需要什么声明。刷新令牌:长 exp,a jti 为撤销跟踪,并尽可能接近其他任何东西如果您的刷新令牌携带角色和配置文件数据,某物's off - it's只曾经呈现给一个端点,应该't复制访问令牌's工作。这种并排检查还抓住了尴尬的错误类,其中两个令牌意外地得到了相同的TTL,这使您的"15分钟访问窗口"进入安全影院。问我怎么知道检查那个。
JWT 与不透明会话令牌:诚实的比较
| JWT | 不透明会话令牌 | |
|---|---|---|
| 无国籍 | 独立;任何带有密钥的服务器都无需查找即可验证 | 器(或像Redis这样的共享商店)必须查找每个请求 |
| 撤销 | 硬 - 有效期至 exp 除非你建立一个重新引入状态的否认主义者 |
Trivial - 删除服务器端记录,令牌立即死亡 |
| 每个请求的大小 | 数百字节到超过一千字节,on 每一个 请求 | ~32.64 字节 |
| 验证发生的地方 | 持(公共)密钥的任何地方 - 非常适合微服务 | 会话存储器居住的任何地方 |
| 声称新鲜 | 发货时间快照;角色变更等待重新发布 | 始终当前 - 读取实时数据 |
| 可调试性 | 立即解码并读取声明 | 设计不透明;需要商店访问 |
I使用JWTs for toolz。dev和I'll仍然告诉你他们're处方过多撤销的故事真的是很糟糕的:当你禁止用户时,他们的访问令牌一直工作到 exp-这正是为什么我的访问令牌活了15分钟,7天刷新令牌是我可以杀死服务器端的东西,那个混合是诚实的模式:用于廉价验证的短命无状态JWT,用于控制的一个有状态检查点如果你're运行一个数据库的巨石,普通会话更简单,更小,并且可以立即撤销 - JWT's分布式验证的超能力正在解决你没有't拥有的问题。
While we'重新对比一下-HS256与RS256一目了然:
| HS256 | RS256 | |
|---|---|---|
| 关键模型 | 一个共同的秘密迹象 並 验证 | 私钥标志,公钥验证 |
| 谁可以铸造代币 | 任何保守秘密的人 | 仅限私钥持有者 |
| 最适合 | 单一服务,发行人=验证者 | 微服务、第三方 IdP、JWKS |
| 签名尺寸/速度 | 更小、更快 | 分布更大、更慢、更安全 |
常见问题
JWT粘贴到在线解码器中安全吗?
仅当解码器运行客户端时。真正的令牌是实时凭据 - 发送给某人's服务器在他们的日志中植入工作密钥。toolz。dev JWT Decoder在您的浏览器中执行所有解码,并且不会传输任何内容;您可以在粘贴时通过查看网络选项卡自行确认此信息。对于解码器,您可以't仅验证、使用过期或测试令牌。
没有秘密你能解码 JWT 吗?
Yes - that's人最怀念的点,头和有效载荷是base64url编码的JSON,编码不是加密,任何人都可以读取每个声明,没有任何密钥,只需创建或验证签名,秘密(或私钥)解码不需要任何东西;信任需要验证。
What's 解码和验证一个 JWT 的区别?
Decoding读取内容:在点上分裂,base64url-decode,解析JSON 验证证明真实性:使用秘密或公钥重新计算或检查签名,确认算法,检查过期 解码器向您显示令牌声称的内容;仅验证,用密钥在服务器端完成,告诉您是否相信它 绝不基于解码但未验证的声明进行授权。
为什么我的 JWT 显示为无效或过期?
最常见的是 exp has reginally passed - 解码一下,查看日期 下一个嫌疑人:一个草率的复制粘贴截断的令牌,一个 aud 或 iss that does't匹配你的验证器's配置,服务器之间的时钟偏差,或者来自旋转密钥的签名。如果解码 exp 看起来不错,但你的代码拒绝了令牌,检查你是否'重新比较秒与毫秒。
JWT 是否加密?
标准 JWT(技术上为 JWS,根据 RFC 7515)是有签名的,而不是加密的。签名检测篡改但不会隐藏任何内容;有效负载可供任何人读取。存在加密变体 (JWE),但在典型的 Web auth 中很少见。实用规则:将每个 JWT 有效负载视为公共,并且永远不会将密码、API 密钥或敏感数据放在其中。
exp 索赔采用什么格式?
exp 是numericdate:自unix时代(1970年1月1日utc)以来的秒,如rfc 7519中所定义。相同 iat 並 nbf。经典的bug是使用javascript's Date.now()的,它返回毫秒 - 产生代币,要么看起来立即过期,要么携带56,000年左右的到期日期如果解码的时间戳显示五位数年份,那's你的bug。
什么是无藻攻击?
RFC 7519 允许不安全的 JWT "alg": "none" 和空签名,旧库信任头's算法字段,所以攻击者剥离签名,集 alg 到 none的, 并通过与伪造的索赔验证。RFC 8725, JWT最佳当前实践, 要求验证者固定一个明确的允许列表的算法在代码和忽略任何令牌要求。
我应该使用 HS256 还是 RS256?
HS256使用一个共享的秘密进行签名和验证-简单而快速,适用于既发出又检查自己的令牌的单一服务RS256用私钥签名,又用公钥验证,因此许多服务可以验证而无法伪造经验法则:monolith,HS256;微服务或第三方身份提供商,RS256。
包裹起来
我建造了 jwt解码器 因为我在构建 toolz。dev 本身时一直需要它 - 同样的 15 分钟访问令牌和 7 天刷新令牌 I've 在整个指南中一直在解剖。它会立即解码,翻译造成 90% 混乱的时间戳,并且从不将您的令牌发送到任何地方。最后一部分是't 功能复选框;对于处理实时凭据的工具,它's 整个设计。
如果代币是您日常调试的一部分,邻居也会赚取保留: 时间戳转换器 对于纪元考古学来说, Base64 转换器 对于戳原始段, json格式化程序 对于索赔斑点,以及 哈希发生器 when you're 处理文摘。为了更广泛的工作流程,我的 API调试工具指南 涵盖解码器在循环中拟合的位置。
And将一句话版本录音到你的显示器:解码告诉你一个令牌说了什么,验证告诉你是否相信。把二人和你混淆'll ship那种最终成为某人开场轶事的bug's博文。这次是我的。



