宝塔拨测博客拨测技术博客
宝塔拨测

JWT 解析出来的是什么?三段结构、时间声明与「解析不等于验签」

JWT 解析,就是把这串用点分隔的文本还原成两段 JSON 加一段签名。这一过程不需要密钥,所以任何拿到字符串的人都能解开——这既是它方便的地方,也是它容易被误读的地方:能解析不代表 token 有效。

宝塔的 JWT 解析工具 做的是第一步:把 Header、Payload、Signature 三段分开显示,把标准声明的名字翻成人能读的说明,并顺手把时间声明换算成本地时间。下面所有内容都来自 2026-11-06 的一次真实运行,正文数字与截图同源。

三段分别是什么:两个 JSON 加一段签名

按 RFC 7519 与 RFC 7515 的定义,JWT 的紧凑格式是 base64url(header) + "." + base64url(payload) + "." + base64url(signature),三段之间用点分隔,缺一段就不是完整的 JWT。

拿一段 HS256 的 token 试:header 是 {"alg":"HS256","typ":"JWT"},payload 里放了 sub、name、role、iss、aud、jti、iat、exp 八个声明。粘贴进工具后:

  • Header 框原样还原出 {"alg":"HS256","typ":"JWT"};
  • Payload 框原样还原出那段 JSON,键的顺序和原始字符串一致,不做排序也不美化;
  • Signature 框只给出 base64url 字符串本身,例如 BU5ezjXOvprC5e7Zri5Na02BMeV7L8EjoPAZ8deHsn8,不做解码——签名是二进制结果的编码,本来就不是 JSON。

宝塔 JWT 解析工具:上方输入框粘贴 token,下面三个框分别还原 Header、Payload 原文与 Signature 串,再往下是 Header、Payload 两张带中文说明的表格

表格第三列「说明」是哪来的:标准声明的固定含义

工具把 header 和 payload 各渲染成一张三列表:键、值、说明。「说明」列不是宝塔自造的词,而是标准里给这些名字定的含义——RFC 7519 第 4.1 节的 Registered Claim Names,以及 OpenID Connect 里常用的几个。

实测这段 payload 的对照结果:

键 值 说明
sub u_9527 Subject
name 张三 Full name
role admin (空,非标准声明)
iss t.bt.cn Issuer
aud api.bt.cn Audience
jti a1b2c3 JWT ID
iat 1793947008 Issued At
exp 1793954328 Expiration Time

两个细节值得注意:

  1. 非标准声明不编说明。role 是自定义键,说明列就是空的,工具不会硬套一个含义。
  2. 数组和对象按 JSON 直出。把 aud 写成数组 ["api.bt.cn","admin.bt.cn"]、再加一个嵌套对象声明,表格里显示的就是 ["api.bt.cn","admin.bt.cn"] 和 {"ip":"1.2.3.4","ua":"curl"} 这样的 JSON 文本,不会变成 [object Object]。

header 的 alg 也一样:值下面用绿色补一行算法全称。实测 HS256 显示 HMAC using SHA-256,RS256 显示 RSASSA-PKCS1-v1_5 using SHA-256,ES256 显示 ECDSA using P-256 and SHA-256,alg 填 none 时显示 No digital signature or MAC performed;这些写法就是 RFC 7518 里的算法名。

时间为什么显示成 11/6/2026 2:36:48 PM

payload 里的 iat、exp、nbf 是 NumericDate,也就是从 1970-01-01T00:00:00Z 起算的 UTC 秒数。工具在原始数字下面补一行换算结果:转成浏览器所在时区,格式是 M/D/YYYY h:mm:ss AM/PM。

截图里 iat 是 1793947008,显示 11/6/2026 2:36:48 PM;exp 是 1793954328,显示 11/6/2026 4:38:48 PM。两个数字差 7320 秒,换算后正好差 2 小时 2 分(4:38:48 减 2:36:48)。

所以「时间对不上」通常不是 token 坏了,而是三件事之一:

  • 浏览器时区不是你预期的时区(这个格式里没有时区标记,看的人容易当成 UTC);
  • 服务器时钟偏了,签发时写进去的秒数本身就错;
  • 把 11/6/2026 读成了别的日期——这是美式月/日/年顺序,不是 ISO 8601 的年月日。

解析成功 ≠ token 有效:签名和过期都不在这一步校验

这是最容易踩的一条。两个实测:

  • 把一段 RS256 token 的签名段换成 Zm9vYmFy(随便一段合法 base64url),工具照常显示 header 表和 payload 表,没有任何报错;
  • 用一段 exp 比当前时间早一天的 token,表格照常渲染,页面上没有任何「已过期」提示。

原因在结构上:验证 HS256 要用密钥重算一次 HMAC 再比对,验证 RS256 要用公钥验签,两者都必须有密钥;而解析器只做 base64url 解码,手里没有任何密钥。所以「解析正常」只能说明格式没坏,说明不了签名没被改、也说明不了没过期。

要真正验签,得在服务端用你自己的密钥或公钥做,或在本地用 openssl 与对应语言的库算。需要生成 HMAC 做对照,可以用宝塔的 HMAC 生成工具;只想知道某段字符串解出来是什么,Base64 编解码工具 更直接。

三种常见报错,各卡在哪一步

① 段数不对:只贴了 header.payload 两段,页面提示「JWT 应为 3 段(以 . 分隔),当前为 2 段」,不渲染任何字段。复制时被截断、或从日志里只抠了前两段,都会这样。

② payload 不是 JSON:把 payload 段换成一段普通文本后,报错是引擎原文 Unexpected token 'h', "this is not json" is not valid JSON(Chrome 实测,其他浏览器内核的措辞可能略有差异)。解出来不是合法 JSON 对象,说明那段 base64 内容本身被改过,或者根本不是 payload。

③ 带上了 Bearer 前缀:从 Authorization: Bearer <token> 整行复制是最常见的场景,报错是 Failed to execute 'atob' on 'Window': The string to be decoded is not correctly encoded.,三段全空。

JWT 解析工具里粘贴带 Bearer 前缀的 token:输入框第一行是 Bearer,下方红色报错 atob 编码不合法,Header、Payload、Signature 三个框和两张表全部为空

处理办法一致:只留三段本身,去掉 Bearer 、换行和首尾空格。JWT 只使用 base64url 字符集,任何额外字符都会让解码直接失败。

payload 是明文,别往里放敏感数据

base64url 是编码不是加密(RFC 4648 第 5 节:用减号和下划线代替加号和斜杠,并去掉等号填充)。任何人拿到 token 都能解出 payload——本文的截图就是在没有任何密钥的情况下解出来的。

所以密码、手机号、身份证号、内部地址这类内容不要放进 payload;确实需要保密,要用 JWE 这种加密结构(五段),而不是普通的签名 JWT。

至于在线解析工具本身:这个页面写明「基于浏览器原生 atob,所有解析在本地完成,不会上传到服务器」,可以把 token 粘进去看结构。但生产环境的 token 往往等价于一次登录,交给任何页面之前,先按你所在环境的合规要求判断。

看 token 的顺序:先 alg,再 exp,最后看账户

拿到一段 token,按这三步看最快:

  1. alg:是不是你服务端预期的算法。看到 none 就该警惕——那表示这段 token 根本没有签名。
  2. exp / iat:换算后的本地时间是否还在有效期内、签发时间是否合理。这一条要配自己的时钟看,工具不会替你判断。
  3. sub / role 等业务声明:解出来的账号和权限是不是你预期的那个。

前两步的结论来自解析后的读数加你自己的校验,第三步才轮到解析工具本身。把工具的输出当成线索,而不是结论。

常见问题

解析成功是不是说明这个 token 有效?

不是。解析只做 base64url 解码,不需要密钥,也不校验签名和有效期。实测把一段 RS256 token 的签名段替换成随便一段 base64url 字符串 Zm9vYmFy,工具照样正常显示 Header 和 Payload 表,没有任何报错;用一段 exp 比当前时间早一天的 token,表格也照常渲染,页面不提示过期。要确认 token 没被改、没过期,得在服务端用自己的密钥或公钥重算签名。

粘贴 token 后报 atob 的 not correctly encoded 是什么原因?

多半是整行复制进来的,带上了 Bearer 前缀或者换行、空格。JWT 三段只含 base64url 字符,实测输入 Bearer 加一个空格再粘 token,工具直接给出 Failed to execute atob on Window: The string to be decoded is not correctly encoded,表格全空。去掉 Bearer 前缀和首尾空白再粘即可。

提示「JWT 应为 3 段」怎么办?

说明字符串被截断了。实测只贴 header.payload 两段,页面提示「JWT 应为 3 段(以 . 分隔),当前为 2 段」,并且不渲染任何字段。按 RFC 7519 的定义,JWT 的紧凑格式固定为三段,中间用点分隔,少了签名段就不是完整的 JWT。

iat 和 exp 为什么显示成 11/6/2026 2:36:48 PM 这种格式?

payload 里的 iat、exp、nbf 是 NumericDate,即 UTC 秒数。工具把秒换算成浏览器本地时区,再按 M/D/YYYY h:mm:ss AM/PM 的格式显示在原始数字下面。截图里 iat 是 1793947008、显示为 11/6/2026 2:36:48 PM,exp 是 1793954328、显示为 11/6/2026 4:38:48 PM,两者正好差 7320 秒(2 小时 2 分)。对不上时先查浏览器时区,别把它当 ISO 8601 用。

能在 JWT 的 payload 里放密码、手机号吗?

不要放。payload 只是 base64url 编码,不是加密(RFC 4648 第 5 节用减号和下划线代替加号和斜杠,并去掉等号填充),任何人拿到 token 都能解出明文——本文截图就是在没有任何密钥的情况下解出来的。需要保密的内容要用 JWE 这类加密结构,而不是普通的签名 JWT。

怎么快速看出 token 用的是哪种算法?

看 header 里的 alg。工具会在值下面用绿色补上算法全称:HS256 是 HMAC using SHA-256,RS256 是 RSASSA-PKCS1-v1_5 using SHA-256,ES256 是 ECDSA using P-256 and SHA-256,alg 为 none 时显示 No digital signature or MAC performed;实测这种 token 把签名段直接留空也照常解析、不报错。

JWTJWT解析Tokenbase64urlHS256宝塔拨测