提示

JWT 解码与验证:我这样调试“令牌过期”和“签名无效”

前后端分离项目里,JWT 是最常见的身份认证方案。有一次前端同事说“接口返回 401,令牌无效”,我让他把 JWT 字符串发给我。我打开在线工具解码一看,过期时间戳是上周的,原来是 token 没刷新。后来我干脆在 VidDown 上写了一个 JWT 解码与验证工具,支持本地解码、自动转换时间戳、HS256 签名验证。这篇文章把 JWT 的结构、工具用法、以及排障经验分享出来。

一、这个工具能做什么?

JWT 解码与验证工具用于:

  • 解码 JWT:将 JWT 字符串的三部分(Header、Payload、Signature)分别 Base64Url 解码,显示 JSON 内容。
  • 时间戳自动转换:Payload 中的 expiatnbf 等时间戳自动转为可读的本地时间。
  • HS256 签名验证:输入 Secret(密钥),校验签名是否匹配,判断令牌是否被篡改。
  • 结构分析:显示三段各自的长度,帮助快速识别格式问题。

🌐 在线使用:https://www.viddown.cn/tools/jwt-tool/

💡 术语解释
- JWT(JSON Web Token):一种紧凑的、自包含的令牌格式,常用于身份认证和信息交换。
- Header:头部,包含签名算法和令牌类型。
- Payload:载荷,存放用户身份信息、过期时间等声明。
- Signature:签名,用于验证令牌未被篡改。
- HS256:HMAC-SHA256 对称签名算法,需要客户端和服务端共享一个密钥(Secret)。
- RS256:RSA-SHA256 非对称签名算法,使用私钥签名、公钥验证。

二、JWT 长什么样?

一个典型的 JWT 字符串由三个点号分隔的 Base64Url 字符串组成:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
- 第一部分(Header):`eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9`
- 第二部分(Payload):`eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ`
- 第三部分(Signature):`SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c`

解码后的 Header 通常为:

{
  "alg": "HS256",
  "typ": "JWT"
}

Payload 通常为:

{
  "sub": "1234567890",
  "name": "John Doe",
  "iat": 1516239022
}

iat 是 issued at(签发时间)的时间戳。

三、如何使用这个工具?

3.1 解码 JWT

1、复制完整的 JWT 字符串。
2、粘贴到工具输入框。
3、点击 「解码」。
4、工具会展示 Header 和 Payload 的格式化 JSON,其中时间戳会自动转为本地时间(如 2024-01-01 10:30:00)。

3.2 验证 HS256 签名

1、解码后,工具会显示使用的算法(例如 HS256)。
2、在下方输入 Secret(密钥,通常由后端提供)。
3、点击 「验证签名」。
4、工具会提示“签名有效”或“签名无效”。
🔐 安全提示:所有操作均在浏览器本地完成,Secret 不会上传至任何服务器。你也可以在无网络环境下使用。

3.3 查看各部分长度

工具会显示三段各自的长度,比如:

Header长度: 120 字符
Payload长度: 98 字符
Signature长度: 86 字符

如果长度异常(比如 Payload 为空或 Signature 长度不对),可能是 token 被截断或格式错误。

四、JWT 的常见声明(Claim)

声明  全称  含义  示例
iss Issuer  签发者 "iss": "https://auth.example.com"
sub Subject 主题(通常是用户 ID)    "sub": "user123"
aud Audience    接收方 "aud": "api.example.com"
exp Expiration Time 过期时间(Unix 时间戳)  "exp": 1735689600
nbf Not Before  生效时间    "nbf": 1735603200
iat Issued At   签发时间    "iat": 1735600000
jti JWT ID  唯一标识    "jti": "abc-123"

工具会自动识别 exp、iat、nbf、auth_time 等时间戳字段,转换为可读的日期时间。

五、实战场景

场景1:调试“令牌过期”

前端调用接口返回 401,后端日志提示 jwt expired。把 token 粘贴到工具,查看 exp 字段的转换结果:

"exp": 1735689600    2025-01-01 00:00:00

当前时间已经超过这个时间,说明 token 确实过期了。需要刷新 token 或重新登录。

场景2:验证签名是否正确

修改了 Payload 中的某个字段(比如把 admin 改为 user),然后重新编码,发现服务端仍然返回签名无效。用工具解码后发现 Signature 部分不变,而 Payload 变了,签名不匹配。说明需要重新生成签名。

场景3:检查算法是否被降级攻击

有些攻击者会把 alg 改成 none 来绕过签名验证。工具解码后如果看到 "alg": "none",应立即警惕。服务端应当拒绝这种 token。

场景4:复制 JWT 中所有声明

点击 「复制全部JSON」 可以合并 Header 和 Payload 的 JSON 内容,方便粘贴到文本或文档中做记录。

六、技术实现(给开发者看)

6.1 JWT 解码(前端实现)

function decodeJWT(token) {
    const parts = token.split('.');
    if (parts.length !== 3) throw new Error('Invalid JWT format');

    const header = JSON.parse(atob(parts[0]));
    const payload = JSON.parse(atob(parts[1]));
    return { header, payload };
}

atob 是浏览器自带的 Base64 解码函数。注意 Base64Url 需要将 - 替换为 +,_ 替换为 /,并补齐 padding。

6.2 时间戳转换

function timestampToDate(ts) {
    return new Date(ts * 1000).toLocaleString();
}

工具会递归遍历 payload 对象,找到以 _time 结尾的字段或常见的声明名,进行转换。

6.3 HS256 签名验证

async function verifyHS256(token, secret) {
    const [headerB64, payloadB64, signature] = token.split('.');
    const data = `${headerB64}.${payloadB64}`;
    const encoder = new TextEncoder();
    const key = await crypto.subtle.importKey(
        'raw', encoder.encode(secret),
        { name: 'HMAC', hash: 'SHA-256' }, false, ['sign']
    );
    const signatureBytes = Uint8Array.from(atob(signature.replace(/-/g, '+').replace(/_/g, '/')), c => c.charCodeAt(0));
    const isValid = await crypto.subtle.verify('HMAC', key, signatureBytes, encoder.encode(data));
    return isValid;
}

现代浏览器都支持 Web Crypto API,可以本地计算签名,不依赖后端。

七、问题汇总(开发和使用中踩过的坑)

1. Invalid JWT format

原因:粘贴的字符串不是三段式,或者包含了换行、空格。
解决:检查字符串格式,确保三个点号都存在。工具会自动去除首尾空格。

2. 解码后乱码

原因:部分 JWT 的 Header 或 Payload 包含非 UTF-8 字符,或者 Base64Url 解码错误。
解决:先检查 JWT 是否被截断。如果字符没问题,可能是用了非标准编码。工具会捕获异常并提示。

3. 验证签名时,HS256 需要 Secret 是什么?

解释:HS256 是对称算法,签发 token 时用的密钥(secret)就是验证时用的同一个字符串。通常在服务端配置中(如 JWT_SECRET=mysecret)。你需要从后端获取这个 secret 才能验证。

4. 工具能验证 RS256 签名吗?

当前版本:不支持 RS256,因为需要公钥(PEM 格式)进行验证,后续会考虑增加 RSA 公钥验证功能。目前仅支持 HS256。

5. 时间戳转换显示时区不对

原因:toLocaleString() 使用浏览器本地时区。如果你在不同时区的设备上查看,时间会不同。
解决:工具显示的是本地时间,通常用于调试。如果需要 UTC 时间,可以额外标注。

6. 为什么有些 JWT 解码后看到 "typ": "JWT",有些没有?

解释:typ 是可选的,大多数实现会忽略。但有的 RFC 建议包含它。

八、与命令行工具对比

工具  优点  缺点
本工具(网页版)    可视化,时间戳自动转换,安全本地处理  需要网络(首次加载),不支持 RS256
jq + base64 手动解码    纯命令行    步骤繁琐,需手工处理 Base64Url 和 padding
jwt-cli 命令行,功能丰富    需要安装 Node.js

手动解码 JWT(命令行)

# 分解 JWT
TOKEN="eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
echo $TOKEN | cut -d. -f1 | base64 -d 2>/dev/null | jq .
echo $TOKEN | cut -d. -f2 | base64 -d 2>/dev/null | jq .

缺点:需要处理 Base64Url 替换 - _ 和补齐 =,容易出错。

九、一点小建议

1、不要在日志中打印完整 JWT,因为 Payload 可能包含敏感信息(如用户 ID、角色)。可以只打印前几位或记录长度。
2、设置合理的过期时间:exp 不要太长(几小时到几天),也不要太短(几秒钟会频繁刷新)。
3、使用 HTTPS 传输 JWT,避免中间人截获。
4、验证签名是必须的:服务端每次收到 JWT 都要验签,否则任何人都可以伪造令牌。
5、不要将 Secret 放在前端代码中:HS256 的 secret 一旦泄露,攻击者可以签发任意 token。
6、定期轮换 secret:降低泄露后的影响范围。

十、总结

JWT 是现代 Web 认证的核心技术,但调试它往往需要频繁解码、查时间戳、验签名。VidDown 的这个工具提供本地解码、时间戳智能转换、HS256 验签三大核心功能,让你不必再手动 Base64 解码。

如果你还没试过,现在就去 https://www.viddown.cn/tools/jwt-tool/ 体验一下,把你的 JWT 丢进去看看里面到底写了什么。

附录:JWT 调试快速检查清单

检查项 命令/操作
查看 Header 算法    解码后检查 alg 字段
查看过期时间  解码后找到 exp,看是否小于当前时间
查看签发时间  解码后找到 iat,看是否在合理范围
验证 HS256 签名 输入 Secret,点击验证
检查 Payload 大小   JWT 总体不能太大(通常 < 8KB)
顶部
×
🔖
收藏本站
将本站添加到浏览器书签,方便下次访问
Ctrl + D (Windows/Linux)
+ D (Mac)