如何解码 JWT
- 把令牌粘贴到 令牌 中。整行
Authorization: Bearer eyJ…也可以,两侧带引号或令牌被拆成多行同样没问题。 - 查看结果。三个部分用不同颜色显示,头部和载荷已格式化,声明 列出每个声明及其含义。
exp、iat等时间会显示为本地时间、UTC 时间以及“1 小时后过期”这样的相对时间。 - 要校验签名,在 验证签名 下粘贴共享密钥或公钥。字段标签随令牌的
alg变化:HS256/384/512 显示 密钥,其他算法显示 公钥。 - 留意警告。工具会标出
alg: none、已过期的令牌、未来的nbf、缺少exp、以毫秒表示的时间,以及指向远程密钥(jku、x5u)或内嵌自身密钥(jwk)的头部。
也可以用 /jwt-decoder/#token=eyJ… 这样的链接直接打开令牌。# 之后的部分是 URL 片段,浏览器从不把片段发送给服务器。
令牌里有什么
签名的 JWT(紧凑形式的 JWS)是用点号连接的三个 Base64URL 字符串:header.payload.signature。下面是一个真实的 HS256 令牌,签发于 2026 年 1 月 1 日,有效期一小时:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJodHRwczovL2F1dGguZXhhbXBsZS5jb20iLCJzdWIiOiJ1c2VyLTQyIiwiYXVkIjoiYXBpLmV4YW1wbGUuY29tIiwiaWF0IjoxNzY3MjI1NjAwLCJleHAiOjE3NjcyMjkyMDB9.U3Gb12hN5Fnpi1rJ7nrkUzIEidkg-hPnbgiIpTah3R4
第一部分解码为头部 {"alg":"HS256","typ":"JWT"}。第二部分是载荷:
{"iss":"https://auth.example.com","sub":"user-42","aud":"api.example.com","iat":1767225600,"exp":1767229200}
第三部分是对前两部分计算的 32 字节 HMAC-SHA256,所以头部或载荷哪怕改动一个字符,签名都会失效。几乎每个 JWT 都以 eyJ 开头,也就是 {" 的 Base64。各部分使用 URL 安全字母表(- 和 _),不带 = 填充;JWT 如何使用 Base64URL 演示了如何手动解码一个令牌。
JWS 还是 JWE:签名令牌与加密令牌
大多数 JWT 是 JWS:经过签名,任何人都能读取。JWE 是加密的,由五部分而不是三部分组成:header.encrypted_key.iv.ciphertext.tag。它的头部仍然可读,其中写明了密钥管理算法(alg,例如 RSA-OAEP-256 或 dir)和内容加密算法(enc,例如 A256GCM)。
在这里粘贴 JWE,工具会解码头部,显示 alg、enc 和 kid,并把载荷标记为已加密。解密它需要接收方的私钥或共享密钥,而本工具不解密 JWE。如果你需要一个用户无法读取载荷的令牌,JWE 才是正确的格式;把机密信息放进 JWS 载荷,跟明文存放没有区别,详见 Base64 不是加密。
JWT 声明参考
前七行是 RFC 7519 中的注册声明;其余的在 OpenID Connect 和 OAuth 令牌中很常见。所有时间值都是 NumericDate:自 1970-01-01 UTC 起的整秒数,绝不是毫秒。
| 声明 | 名称 | 要检查什么 |
|---|---|---|
iss | 签发者 | 谁创建并签名了令牌。必须与你信任的签发者一致。 |
sub | 主体 | 令牌所涉及的用户或实体,通常是用户 ID。 |
aud | 受众 | 令牌的目标接收方。可以是字符串或数组;你的 API 应拒绝发给别人的令牌。 |
exp | 过期时间 | 过了这个时刻,令牌必须被拒绝。 |
nbf | 生效时间 | 在这个时刻之前,令牌尚未生效。 |
iat | 签发时间 | 签发的时间。可用于限制令牌的最长使用时长。 |
jti | JWT ID | 唯一 ID,用于检测重放或吊销单个令牌。 |
azp | 授权方 | 令牌所颁发给的 OAuth 客户端(OpenID Connect)。 |
scope、scp | 范围 | 已授予的 OAuth 范围;scp 是 Microsoft 和 Okta 的写法。 |
nonce | Nonce | 把 OpenID Connect ID 令牌与请求它的登录请求绑定在一起。 |
auth_time | 认证时间 | 用户实际登录的时间,可能比 iat 早很多。 |
roles、groups | 角色、组 | 身份提供方添加的授权数据。 |
tid、oid | 租户、对象 ID | Microsoft Entra ID 的租户和用户对象标识符。 |
头部有自己的参数:alg(签名算法)、typ(通常是 JWT,OAuth 访问令牌则为 at+jwt)、kid(签名所用的密钥)和 cty(令牌嵌套在另一个令牌中时设为 JWT)。
验证签名:该粘贴哪个密钥
只有签名校验才能证明令牌的来源,所需的密钥取决于头部中的算法系列。
alg | 要粘贴的密钥 | 签名长度 |
|---|---|---|
| HS256、HS384、HS512 | 共享密钥。按其存储方式把 密钥格式 设为文本、Base64/Base64URL 或 Hex;oct JWK 也可以。 | 32、48、64 字节 |
| RS256–RS512、PS256–PS512 | RSA 公钥:PEM BEGIN PUBLIC KEY、PEM BEGIN RSA PUBLIC KEY、X.509 证书、JWK 或 JWKS。 | 等于密钥长度;RSA-2048 为 256 字节 |
| ES256、ES384、ES512 | P-256、P-384 或 P-521 曲线上的 EC 公钥,以上任一形式均可。 | 64、96、132 字节(原始 r‖s) |
| EdDSA | PEM 或 OKP JWK 形式的 Ed25519 公钥。 | 64 字节 |
对于身份提供方签发的令牌,公钥位于 https://<issuer>/.well-known/openid-configuration 中列出的 jwks_uri。本页无法连接其他服务器,因此无法获取该 URL,所以请在新标签页中打开它并粘贴 JSON。使用 JWKS 时会按 kid 选择密钥;如果令牌没有 kid,会逐一尝试所有兼容的密钥。
DER 格式的 ECDSA 签名(ES256 通常为 70 到 72 字节,而不是 64 字节)会被识别、转换并给出提示,因为 JWS 要求原始的 r‖s 格式,很多库会拒绝 DER。粘贴私钥也可以(会从中推导出公钥部分),不过验证从来不需要私钥。由于 WebCrypto 不支持,ES256K 和 Ed448 无法使用。
如果一个 HS256 令牌用你确信无误的密钥仍然验证失败,往往是因为密钥在配置中以 Base64 编码存储,服务器使用前会先解码。把 密钥格式 切换为 Base64 再试一次。
为什么在这里解码令牌是安全的
Authorization 请求头中的 JWT 在过期之前都是有效凭据。如果你把线上生产环境的令牌粘贴到一个会把它发往服务器的网页中,运营那台服务器的人就能拿它重放请求、调用你的 API,而且从外部通常无法判断一个页面是否这样做。
在本页,解码和 WebCrypto 签名校验都在你的浏览器中运行。页面还带有内容安全策略,其 connect-src 只允许 /cdn-cgi/rum,即 Cloudflare Web Analytics 统计页面浏览的地址,所以浏览器会拦截页面可能发起的其他所有 fetch、XHR、WebSocket 和 beacon 请求。你可以自己验证:打开开发者工具的“网络”标签页再粘贴令牌,或者在页面加载后关闭 Wi-Fi,继续解码。
即便如此,也尽量使用测试令牌或即将过期的令牌;如果生产令牌被粘贴到了你无法控制的地方,请吊销该会话。
过期令牌、nbf 与时钟偏差
exp 和 nbf 是对照验证方的时钟检查的,而时钟会漂移。如果签发服务器的时钟快了 30 秒,令牌到达时 iat 和 nbf 可能处在未来,从而被当作“尚未生效”拒绝。因此 JWT 库都允许少量容差:PyJWT 中是 leeway,jsonwebtoken 和 jose 中是 clockTolerance。
本解码器把这些时间与你设备的时钟对照,并用文字描述结果:exp 显示“2 天前已过期”,nbf 显示“尚未生效”及剩余时间,iat 处于未来时则给出警告,提示签发方的时钟有问题。1767229200000 这样的值会被标记为毫秒;如果按秒读取,它会落在几万年之后,这是代码用了 Date.now() 而不是 Math.floor(Date.now() / 1000) 时的常见错误。
只读取声明而不验证,用 Python 标准库就够了:
import base64, json
def b64url_decode(part):
return base64.urlsafe_b64decode(part + "=" * (-len(part) % 4))
header_b64, payload_b64, signature_b64 = token.split(".")
print(json.loads(b64url_decode(payload_b64)))
在 Node.js 中,JSON.parse(Buffer.from(payload_b64, "base64url").toString("utf8")) 的作用相同。做授权决策时,请使用会验证签名并限定可接受算法的库。更多代码片段见 Python 和 Node.js 页面。
要生成或重新签名测试用的令牌,请使用 JWT 编码器。单个分段可以用 Base64URL 解码器 单独解码。
常见问题
没有密钥能解码 JWT 吗?
能。头部和载荷只是经 Base64URL 编码的 JSON,任何持有令牌的人都能读取。只有在校验签名是否真实时,才需要密钥或公钥。
为什么解码器提示签名无效?
通常是密钥不对或格式不对。对于 HS256,检查密钥是以 Base64 还是 hex 形式存储的,并相应修改 密钥格式。对于 RS256 或 ES256,确认公钥与令牌的 kid 对应;如果粘贴的是整个 JWKS,工具会自动按 kid 选择密钥。
怎样查看 JWT 什么时候过期?
看 exp 声明。它是自 1970-01-01 UTC 起的秒数,声明表会把它显示为本地时间、UTC 时间以及“2 天前已过期”这样的相对时间。没有 exp 的令牌本身永不过期。
这个工具能解密 JWE 吗?
不能。对于五段式的加密令牌,它会解码受保护的头部并显示 alg、enc 和 kid。解密载荷则需要接收方的私钥或共享密钥。
粘贴令牌时,它会被发送到任何地方吗?
不会。解码和验证都在你的浏览器中通过 JavaScript 和 WebCrypto 完成,页面的内容安全策略会阻止除 Cloudflare 的浏览量统计以外的所有网络请求。