3. JWT 安全风险:Token 泄露、重放攻击、密钥泄露、算法混淆攻击及防御措施

JWT(JSON Web Token)作为无状态认证的核心载体,其安全性直接决定了后端权限矩阵的可靠性。在实际生产环境中,JWT 面临多种攻击向量,包括 Token 泄露、重放攻击、密钥泄露以及算法混淆攻击。本章将系统分析这些风险的技术原理,并给出经过验证的防御策略。

3.1 四大核心风险与攻击原理

下表总结了 JWT 生命周期中最高频的四种安全威胁,以及它们对应的攻击入口和影响范围:

风险类型 攻击原理 攻击入口 影响范围
Token 泄露 攻击者通过中间人攻击、XSS、日志泄露等方式获取 JWT 字符串 网络传输、浏览器存储、服务端日志 攻击者可完全冒充用户身份,访问所有授权资源
重放攻击 攻击者截获合法 JWT 后,在有效期内重复使用该 Token 发起请求 网络嗅探、中间人代理 非授权操作重复执行,如重复转账、重复下单
密钥泄露 签名密钥(HS256 的对称密钥或 RS256 的私钥)被攻击者获取 代码仓库、配置文件、密钥管理服务漏洞 攻击者可伪造任意 JWT,彻底瓦解认证体系
算法混淆攻击 攻击者篡改 JWT 头部中的 alg 字段,诱使服务端使用弱算法或无签名验证 JWT 解析库的算法信任缺陷 绕过签名校验,伪造任意 payload
关键认知:JWT 本身并不提供加密传输或防重放机制。所有安全属性必须通过传输层(HTTPS)、存储策略(HttpOnly Cookie)和应用层逻辑(nonce、jti)来补充。将安全责任完全交给 JWT 本身是架构设计中的常见误区。

3.2 Token 泄露与防御措施

Token 泄露是最常见的安全事故。JWT 一旦泄露,攻击者即可在 Token 有效期内完全接管用户会话。防御需要从存储、传输和生命周期三个维度入手:

  • 传输层强制加密:所有 JWT 必须通过 HTTPS 传输,禁止在 HTTP 明文通道中传递 Token。在微服务间调用时,也应使用 mTLS 或服务网格加密。
  • 存储策略选择:优先使用 HttpOnly + Secure + SameSite=Strict 的 Cookie 存储 JWT,避免使用 localStorage 或 sessionStorage,后者易受 XSS 攻击读取。
  • 短期 Token + 刷新机制:Access Token 有效期建议控制在 15 分钟以内,配合 Refresh Token 实现无感续期。即使 Access Token 泄露,攻击窗口也被大幅压缩。
  • Token 指纹绑定:在 JWT payload 中嵌入客户端指纹(如 User-Agent 哈希、IP 段、TLS 会话 ID),服务端验证时比对指纹,防止 Token 被跨设备使用。
实践建议:对于高安全场景(如金融、医疗),建议引入 Token 绑定(Token Binding)技术,将 JWT 与 TLS 会话强绑定,即使 Token 被复制到其他设备也无法使用。

3.3 重放攻击与防御措施

重放攻击的核心在于攻击者利用 JWT 的“无状态”特性——服务端默认不记录已使用的 Token。防御的关键是引入一次性或时间敏感机制:

防御策略 实现方式 适用场景 性能影响
jti + 黑名单 JWT 生成时分配唯一 ID(jti),服务端维护已使用 jti 的缓存黑名单 关键写操作(转账、删除) 中等(需缓存查询)
nonce 挑战 服务端下发一次性 nonce,客户端签名到 JWT 中 API 网关、敏感接口 较高(需存储 nonce)
时间窗口校验 严格校验 iat 和 exp,拒绝过期 Token;结合 nbf 防止未来 Token 所有接口 极低(仅时间比较)
请求签名(HMAC) 将请求体、时间戳、路径等参数签名后放入 JWT 或请求头 幂等性要求高的接口 中等(需验签计算)

以下 SVG 图展示了重放攻击的完整链路以及 jti 黑名单机制的防御原理:

重放攻击链路与 jti 黑名单防御 攻击者 合法客户端 服务端 jti 黑名单缓存 (Redis / 本地缓存) ① 发送 JWT (含 jti) ② 截获 JWT ③ 重放 JWT (相同 jti) ④ 查询 jti 是否已存在 ⑤ jti 已存在,拒绝请求 防御要点 jti 必须在 JWT 生成时分配唯一值(UUID),黑名单缓存时间应等于 Token 有效期,过期自动清除

3.4 密钥泄露与防御措施

密钥泄露是 JWT 体系中最致命的威胁。一旦签名密钥暴露,攻击者可以伪造任意身份和权限。防御策略需要覆盖密钥的生成、存储、轮换和验证全生命周期:

  • 非对称算法优先:使用 RS256 或 ES256 代替 HS256。HS256 是对称算法,签名和验证使用同一个密钥,泄露风险更高;RS256 使用私钥签名、公钥验证,公钥可以安全分发。
  • 密钥管理服务(KMS):将密钥存储在专用的密钥管理服务(如 AWS KMS、HashiCorp Vault)中,避免硬编码在配置文件或代码仓库中。应用运行时通过 API 获取密钥,不直接接触密钥明文。
  • 定期轮换密钥:建立密钥轮换机制,建议每 90 天轮换一次。轮换时保留旧密钥的验证能力(通过 kid 字段标识),直到所有使用旧密钥签发的 Token 过期。
  • 密钥泄露应急响应:准备密钥吊销列表(Key Revocation List),一旦发现泄露,立即将泄露密钥的 kid 加入黑名单,并强制所有用户重新登录。
架构级建议:在微服务架构中,建议将 JWT 签名和验证职责集中在 API 网关或认证服务中,下游服务只信任网关传递的已解析用户信息(如 user_id、roles),不直接接触 JWT 签名逻辑。这样可以大幅缩小密钥的使用范围。

3.5 算法混淆攻击与防御措施

算法混淆攻击(Algorithm Confusion Attack)利用了 JWT 解析库对 alg 头部字段的信任缺陷。攻击者将 alg 从 RS256 改为 HS256,并使用已知的公钥作为 HMAC 的密钥来签名 Token。如果服务端未严格校验算法类型,就会使用公钥作为 HS256 的密钥进行验证,从而通过伪造的 Token。

防御算法混淆攻击的核心措施:

防御措施 实现方式 代码示例(Java / jjwt)
白名单算法校验 在 JWT 解析器中显式指定允许的算法列表,拒绝未注册的算法
Jwts.parserBuilder()
  .setSigningKey(publicKey)
  .requireIssuer("auth-service")
  .setAllowedClockSkewSeconds(30)
  .build()
  .parseClaimsJws(token);
密钥类型绑定 根据算法类型选择对应的密钥对象,禁止将 RSA 公钥用于 HMAC 验证
if (algorithm.startsWith("HS")) {
  // 使用对称密钥
} else if (algorithm.startsWith("RS")) {
  // 使用 RSA 公钥
} else {
  throw new SecurityException("不支持的算法");
}
禁用 none 算法 显式禁止 alg: "none",拒绝无签名 Token
// 在解析器中设置
parser.setAllowedClockSkewSeconds(0);
// 或使用安全库版本 >= 0.11.0

以下 SVG 图对比了正常验证流程与算法混淆攻击的流程差异:

算法混淆攻击:正常流程 vs 攻击流程 正常验证流程 JWT 头部: alg=RS256 服务端使用 RSA 公钥 匹配 验证通过 ✅ 攻击流程 JWT 头部: alg=HS256 服务端错误地 使用 RSA 公钥作为 HMAC 密钥 不匹配 验证通过 ❌(伪造成功) 攻击原理 攻击者使用已知的 RSA 公钥作为 HMAC 密钥 对伪造的 JWT 进行签名 服务端未校验算法类型,导致验证通过

3.6 综合防御矩阵

将上述四种风险的防御措施整合为一个统一的防御矩阵,便于架构设计时对照检查:

风险类型 传输层防御 应用层防御 存储层防御 监控与应急
Token 泄露 HTTPS / mTLS 短期 Token + 指纹绑定 HttpOnly Cookie 异常 Token 使用检测
重放攻击 TLS 会话复用限制 jti 黑名单 / nonce — 重复 jti 告警
密钥泄露 — 非对称算法 + KMS 密钥加密存储 密钥轮换 + 吊销列表
算法混淆 — 算法白名单 + 密钥类型绑定 — 异常算法告警
架构落地检查清单:
✅ 所有 JWT 传输是否强制使用 HTTPS?
✅ Access Token 有效期是否 ≤ 15 分钟?
✅ 是否实现了 jti 黑名单或 nonce 机制?
✅ 签名密钥是否存储在 KMS 或 Vault 中?
✅ JWT 解析库是否配置了算法白名单?
✅ 是否禁用了 alg: none?