不喜欢广告? 无广告 今天

JWT 令牌 解码、验证并避免常见错误

发布日期
JWT令牌:解码、验证和避免常见错误 1
广告 移除?
JWT 令牌:解码、验证和避免常见错误

JWT出现在几乎所有现代Web应用中——认证头、刷新流程、API访问控制。它们也是野外最常被误用的标准之一。如果你使用它们进行开发,了解令牌内部是什么、解码和验证实际意味着什么,以及哪些捷径会悄悄破坏你的安全性,这一点是不可或缺的。

JWT的结构

一个JWT由三个base64url编码的字符串用点连接而成:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJ1c2VyXzEyMyIsImVtYWlsIjoiYWxpY2VAZXhhbXBsZS5jb20iLCJpYXQiOjE3MTI3NjQ4MDAsImV4cCI6MTcxMjg1MTIwMH0.4Xr8mNkZQWpH2TvL9uY3sKdJwFqBzEcAoMnRiVePxlU
  • 标头 — 算法和令牌类型(alg, typ)
  • 有效载荷 — 声明(你的应用实际需要的数据)
  • 签名 — 对前两个部分的HMAC或RSA签名

解码后,有效载荷看起来像这样:

{
  "sub": "user_123",
  "email": "alice@example.com",
  "iat": 1712764800,
  "exp": 1712851200
}

这里没有任何内容是加密的。任何持有令牌的人都可以读取这些值。签名只证明令牌在签发后没有被篡改——它并不能隐藏内容。

解码不等于验证

这个区别让比本应如此多的开发者感到困惑。

解码 将令牌按点分割,并对每个部分进行base64url解码。不需要密钥——任何工具或单行代码都可以做到。如果你想在线解码jwt而无需安装任何东西,请将令牌粘贴到 IO Tools JWT Decoder 即可立即获取头部、有效载荷和过期时间的分解信息。

验证 使用密钥(或用于非对称算法的公钥)检查签名是否有效。它还会确认令牌是否未过期,以及声明是否与你的应用程序预期相符。跳过验证并信任解码后的令牌是发生身份验证绕过的方式。

这是一个使用Node.js验证JWT并处理常见边缘情况的代码片段:

import jwt from 'jsonwebtoken';

const SECRET = process.env.JWT_SECRET;

function verifyToken(token) {
  try {
    const payload = jwt.verify(token, SECRET, {
      algorithms: ['HS256'],  // whitelist — never allow 'none'
      audience: 'myapp',      // validate aud claim
    });

    // jwt.verify throws if exp is in the past, but be explicit:
    if (payload.exp < Math.floor(Date.now() / 1000)) {
      throw new Error('Token expired');
    }

    return payload;
  } catch (err) {
    // Never silently swallow verification failures
    throw new Error(`Invalid token: ${err.message}`);
  }
}

值得验证的声明

JWT规范定义了你的服务器应该主动检查,而不仅仅是读取的标准声明:

声明意义是否验证?
exp过期时间戳总是——过期的令牌是一个真实的攻击面
iat签发时间戳可选,适用于最大年龄检查
sub主题(通常是用户ID)是——确认它是否与预期的用户匹配
aud预期受众是——防止服务A的令牌用于服务B

大多数JWT库都会 exp 自动验证——但前提是你必须配置它们。阅读你库的文档。不要假设它默认开启。

让开发者犯错的三个错误

1. 算法 alg: none 攻击

JWT规范允许的算法值是 none,这意味着没有签名。一些库——特别是较旧的库——接受了这一点,并完全跳过了签名验证。攻击者剥离签名,在头部设置 "alg": "none" ,并伪造任意声明。服务器信任它。

修复方法:验证时明确白名单算法。绝不能接受 none。上面的代码片段用此演示了 algorithms: ['HS256'].

2. 未验证过期时间

解码令牌而没有检查有效载荷,并信任其内容 exp 意味着几个月前签发的令牌仍然被接受。如果用户的会话被撤销或攻击者窃取了旧令牌,你的应用程序将永远不会知道。

修复方法:将过期时间视为强制性,而非可选。要检查特定令牌何时失效, IO Tools JWT Expiry Checker 解码 exp 声明,并准确地告诉你还剩多少时间——这对于无需编写代码即可调试刷新流程非常有用。

3. 将JWT存储在localStorage中

localStorage可以被页面上的任何JavaScript读取。单个XSS漏洞就意味着攻击者可以静默地窃取你的用户认证令牌。以下是存储选项的比较:

存储位置XSS风险CSRF风险可从JS访问笔记
localStorage高的没有任何是的不应用于认证令牌
sessionStorage高的没有任何是的与localStorage有相同的风险
httpOnly cookie没有任何中等的最适合认证;与SameSite + CSRF令牌配对
内存中(JS var)低的没有任何是(相同上下文)刷新时丢失;适用于短期令牌

httpOnly cookie根本无法被JavaScript读取,这完全消除了XSS窃取向量。权衡是CSRF暴露,你需要用 SameSite=Strict 或CSRF令牌来处理。

JWT与会话:诚实的权衡

JWT是无状态的——服务器无需查询数据库即可验证它们。这在分布式系统中很有用,因为你不想让每个服务都访问共享的会话存储。

但无状态是有真实代价的:你不能在JWT过期之前撤销它。如果用户登出或被攻破,令牌将一直有效直到 exp。存在变通方法(令牌黑名单、短过期时间+刷新令牌),但它们增加了复杂性,并且通常重新创建了会话存储已经实现的功能。

何时使用JWT: 当你拥有多个独立认证请求的服务,你想将角色/权限直接嵌入令牌中,或者你的令牌是短期且可接受撤销延迟时。

何时使用会话: 当你需要立即撤销(登出必须真正起作用),你正在构建一个服务器渲染的应用,或者简单性超过了无状态的可扩展性时。

几秒内检查令牌

现在需要查看JWT内部是什么?终端:

echo "YOUR.JWT.HERE" | cut -d. -f2 | base64 -d 2>/dev/null | python3 -m json.tool

或者跳过终端——将令牌粘贴到 IO Tools JWT Decoder 以获取头部和有效载荷的格式化分解。这两种方法都没有验证签名;它们只是解码。要快速读取过期时间而无需编写代码,使用 JWT Expiry Checker 可以给出确切的时间戳和剩余时间。

想要享受无广告的体验吗? 立即无广告

安装我们的扩展

将 IO 工具添加到您最喜欢的浏览器,以便即时访问和更快地搜索

添加 Chrome 扩展程序 添加 边缘延伸 添加 Firefox 扩展 添加 Opera 扩展

记分板已到达!

记分板 是一种有趣的跟踪您游戏的方式,所有数据都存储在您的浏览器中。更多功能即将推出!

广告 移除?
广告 移除?
广告 移除?

新闻角 包含技术亮点

参与其中

帮助我们继续提供有价值的免费工具

给我买杯咖啡
广告 移除?