alltools.one
Security
2025-06-12
8 min
alltools.one Team
JWTSecurityAuthenticationAPIWeb Security

JSON Web Token 安全:常见漏洞与修复方案

JWT 是 API 认证的事实标准,但其表面上的简单性隐藏了真正的安全隐患。配置不当的 JWT 曾导致认证绕过、权限提升和数据泄露。本指南涵盖最关键的漏洞以及如何防范它们。

JWT 结构回顾

JWT 由三个 Base64URL 编码的部分组成,以点号分隔:

eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIxMjMiLCJyb2xlIjoiYWRtaW4ifQ.signature
  • Header(头部):算法和令牌类型
  • Payload(载荷):声明(用户数据、过期时间等)
  • Signature(签名):加密验证

使用我们的 JWT 编码器/解码器 检查 JWT 结构。

如需了解 JWT 结构的基础知识,请参阅我们的 JWT 令牌详解 指南。

关键漏洞

1. 算法混淆攻击

最危险的 JWT 漏洞。如果服务器在未经验证的情况下接受令牌中的 alg 头部,攻击者可以:

攻击方式:将算法从 RS256(非对称)更改为 HS256(对称),并使用服务器的公钥签署伪造的令牌:

// 攻击者伪造的令牌
header: { "alg": "HS256", "typ": "JWT" }
payload: { "sub": "admin", "role": "superadmin" }
// 使用服务器的公钥作为 HMAC 密钥进行签名

如果服务器使用公钥作为密钥来验证 HS256 令牌,伪造的令牌就会通过验证。

修复方案:始终显式指定预期的算法:

// 错误 - 接受令牌指定的任何算法
jwt.verify(token, key);

// 正确 - 强制指定算法
jwt.verify(token, key, { algorithms: ['RS256'] });

2. None 算法攻击

某些库接受 "alg": "none" — 一个没有签名的令牌:

// 没有签名的伪造令牌
header: { "alg": "none", "typ": "JWT" }
payload: { "sub": "admin", "role": "superadmin" }
signature: ""  // 空

修复方案:在生产环境中永远不允许 none 算法。显式设置允许的算法白名单。

3. 弱签名密钥

基于 HMAC 的 JWT(HS256/HS384/HS512)的安全性取决于密钥的强度:

// 极差 - 几秒内即可暴力破解
secret = "password123"

// 较弱 - 容易受到字典攻击
secret = "my-jwt-secret"

// 强密钥 - 256+ 位随机数
secret = "a3f2b8c9d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0"

修复方案:HMAC 密钥至少使用 256 位加密随机数。更好的做法是使用非对称密钥(RS256、ES256),因为签名密钥无需共享。

4. 缺少过期时间

没有过期时间的令牌永远不会失效 — 被盗的令牌将授予永久访问权限:

// 错误 - 没有过期时间
{ "sub": "user123", "role": "admin" }

// 正确 - 短过期时间
{
  "sub": "user123",
  "role": "admin",
  "exp": 1705312800,
  "iat": 1705309200,
  "nbf": 1705309200
}

最佳实践:访问令牌在 15-60 分钟内过期。使用刷新令牌(安全存储)来维持长会话。

5. 载荷中包含敏感数据

JWT 载荷是 Base64URL 编码的,而不是加密的。任何人都可以解码它们:

echo "eyJzdWIiOiIxMjMiLCJyb2xlIjoiYWRtaW4ifQ" | base64 -d
# {"sub":"123","role":"admin"}

永远不要包含:密码、API 密钥、信用卡号、个人数据(身份证号、医疗记录)或任何秘密信息。

6. 令牌存储(XSS 与 CSRF)

存储方式XSS 脆弱CSRF 脆弱
localStorage
Cookie(无标志)
HttpOnly Cookie
HttpOnly + SameSite Cookie

推荐方案:将 JWT 存储在 HttpOnlySecureSameSite=Strict Cookie 中。这可以防止 JavaScript 访问(XSS 防御)和跨站请求(CSRF 防御)。

Set-Cookie: token=eyJ...; HttpOnly; Secure; SameSite=Strict; Path=/; Max-Age=3600

安全检查清单

  1. 显式指定算法 — 永远不要从令牌头部接受算法
  2. 拒绝 none 算法 — 始终要求签名
  3. 使用强密钥 — HMAC 使用 256+ 位,RSA 使用 2048+ 位
  4. 设置短过期时间 — 访问令牌 15-60 分钟
  5. 验证所有声明expissaudnbf
  6. 存储在 HttpOnly Cookie 中 — 不要使用 localStorage
  7. 定期轮换密钥 — 制定密钥轮换计划
  8. 永远不要在载荷中存储敏感数据
  9. 使用非对称密钥 用于分布式系统(RS256 或 ES256)
  10. 实现令牌撤销 — 黑名单或短过期时间 + 刷新令牌

非对称与对称签名

方面HS256(对称)RS256(非对称)
密钥共享密钥私钥/公钥对
谁可以签名拥有密钥的任何人仅私钥持有者
谁可以验证拥有密钥的任何人拥有公钥的任何人
密钥分发必须安全共享密钥仅共享公钥
性能更快更慢
最适合单一服务微服务、分布式系统

建议:新应用使用 ES256(ECDSA) — 它提供非对称安全性,性能接近 HMAC。

令牌撤销策略

JWT 在设计上是无状态的 — 没有内置的撤销方式。以下是可用策略:

  1. 短过期时间:如果令牌在 15 分钟内过期,被盗令牌的有效窗口有限
  2. 刷新令牌轮换:每次使用时签发新的刷新令牌;如果刷新令牌被使用两次,则撤销所有令牌
  3. 黑名单:存储已撤销令牌的 ID(jti 声明),并在每次请求时检查
  4. 令牌版本控制:在声明中包含版本号;用户注销时递增版本号

常见问题

我应该使用 JWT 还是会话 Cookie 进行认证?

对于传统 Web 应用程序,会话 Cookie 更简单、更安全 — 服务器控制会话生命周期,撤销是即时的。JWT 更适合无状态 API、微服务和移动应用,在这些场景中服务器端会话存储不切实际。如果选择 JWT,请实施本指南中的安全措施。

JWT 的理想过期时间是多长?

访问令牌:15-60 分钟。更短的时间更安全,但需要更频繁的刷新。刷新令牌:1-30 天,存储在 HttpOnly Cookie 中。一次性令牌(邮箱验证、密码重置):1-24 小时。

相关资源

Published on 2025-06-12
JSON Web Token Security: Common Vulnerabilities and Fixes | alltools.one