Developer Tools

JWT 详解:结构、安全性与常见错误

深入了解 JSON Web Token 的工作原理、内部结构,以及开发者在生产环境中常踩的安全陷阱。

8分钟阅读

电路板上的安全锁

JSON Web Token(JWT)无处不在——它为全球范围内的单页应用、移动应用和微服务架构提供身份验证支持。然而,它也是 Web 开发中最容易被误解的安全基础组件之一。一旦用错,可能导致身份验证绕过、权限提升,甚至账户被完全接管。

什么是 JWT?

JWT 是一种紧凑、URL 安全的字符串,用于编码一组声明(claims)——即关于用户或会话的断言。它看起来像这样:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c

它由三个以点号分隔的 Base64URL 编码段组成:

  1. Header(头部) — 算法与令牌类型
  2. Payload(负载) — 声明内容(用户数据)
  3. Signature(签名) — 完整性的加密证明

将任意 JWT 粘贴到我们的 JWT Decoder 中,即可立即检查所有三个部分,无需请求任何外部服务器。

JWT 的结构解析

Header(头部)

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

alg 指定签名算法,常见值包括:

  • HS256 — 基于 SHA-256 的 HMAC(对称加密,使用单一密钥)
  • RS256 — 基于 SHA-256 的 RSA(非对称加密,使用公私钥对)
  • ES256 — 基于 SHA-256 的 ECDSA(非对称加密,密钥更短)

Payload(负载)

{
  "sub": "user_abc123",
  "email": "alice@example.com",
  "role": "admin",
  "iat": 1711670400,
  "exp": 1711756800
}

标准注册声明:

声明 含义
sub Subject(令牌所描述的主体)
iss Issuer(令牌的签发方)
aud Audience(令牌的接受方)
exp Expiration time(过期时间,Unix 时间戳)
iat Issued at(签发时间,Unix 时间戳)
nbf Not before(在此时间之前不接受该令牌)

Signature(签名)

以 HS256 为例:

HMAC-SHA256(base64url(header) + "." + base64url(payload), secret)

签名用于证明令牌未被篡改。Payload 并未加密——它只是经过编码。 任何人都可以读取其内容。

切勿在 JWT Payload 中存储密码或信用卡号等敏感数据。

HS256 与 RS256:如何选择?

HS256 使用单一共享密钥。所有需要验证令牌的服务都必须持有相同的密钥。对于单体应用来说简单易用,但在多服务架构中存在风险——一旦任何一个服务被攻破,攻击者便可伪造令牌。

RS256 使用非对称密钥。认证服务器用私钥签名,其他所有服务用公钥验证。即使某个消费服务被攻破,攻击者也无法伪造令牌。对于多服务系统,推荐使用 RS256。

alg: none 漏洞

这是最臭名昭著的 JWT 攻击之一。某些早期版本的库会接受 "alg": "none" 且不带签名的令牌,并将其视为有效。攻击者可以构造如下 Header:

{ "alg": "none" }

配合任意 Payload,从而完全绕过身份验证。

修复方案: 在 JWT 库中始终显式指定允许的算法,绝不接受 none

// ❌ 危险写法
jwt.verify(token, secret);

// ✅ 安全写法——仅接受 HS256
jwt.verify(token, secret, { algorithms: ["HS256"] });

算法混淆攻击

另一个严重漏洞:如果你的库从 Header 中自动检测算法,攻击者可以将 RS256 改为 HS256,并使用公钥作为 HMAC 密钥对令牌进行签名(而公钥,顾名思义,是公开的)。

修复方案: 始终在代码中硬编码预期的算法,绝不信任 alg Header。

令牌存储:JWT 应该放在哪里?

存储方式 XSS 风险 CSRF 风险 说明
localStorage 页面上的任意 JS 均可访问
sessionStorage 标签页关闭后清除
HTTP-only Cookie Web 应用首选;配合 SameSite=Strict 使用
内存(变量) 页面刷新后丢失;适合单页应用

对于 Web 应用,配合 SameSite=Strict 的 HTTP-only Cookie 是最安全的选择。对于原生应用,应使用系统提供的安全存储 API(如 Keychain、Keystore)。

过期时间与刷新令牌

短期访问令牌(5–15 分钟)与长期刷新令牌配合使用是标准模式:

  1. 用户登录 → 服务器签发访问令牌(15 分钟)+ 刷新令牌(7 天,存储于数据库)
  2. 客户端使用访问令牌发起 API 请求
  3. 访问令牌过期后,客户端发送刷新令牌 → 获取新的访问令牌
  4. 用户登出时,在数据库中使刷新令牌失效

这种模式能有效限制访问令牌被盗后的危害范围。

吊销 JWT

JWT 是无状态的——一旦签发,在过期之前始终有效。这是一种权衡。提前吊销的方案:

  • 黑名单 — 将已失效的 JTI(JWT ID)存储在 Redis 中,每次请求时进行检查。
  • 短期过期 — 5 分钟的令牌可将影响范围降至最低。
  • 刷新令牌轮换 — 检测已轮换令牌的重复使用,将其视为被盗迹象。

使用 JWT Decoder 进行调试

在排查身份验证问题时,可使用我们的 JWT Decoder 来:

  • 无需编写代码即可检查完整的 Payload
  • 以可读格式查看过期时间戳
  • 确认正在使用的签名算法
  • 发现意外或缺失的声明

所有解码操作均在浏览器本地完成——令牌不会离开你的设备。

JWT 安全检查清单

  • 在多服务架构中使用 RS256 或 ES256
  • 在 JWT 库中显式指定允许的算法
  • 每次请求都验证 expissaud 声明
  • Web 应用中将令牌存储于 HTTP-only Cookie
  • 使用较短的访问令牌有效期(15 分钟或更短)
  • 实现带重复使用检测的刷新令牌轮换机制
  • 切勿在 Payload 中存放敏感数据
  • 全程使用 HTTPS——明文传输的令牌等同于没有任何认证

JWT 功能强大且便于使用,但其安全性完全取决于实现方式。理解其结构、掌握已知攻击手段,你的身份验证层才能真正坚不可摧。