JWT 并不安全——除非你了解 JWS 和 JWE

发布: (2025年12月28日 GMT+8 12:19)
6 分钟阅读
原文: Dev.to

抱歉,我没有看到需要翻译的正文内容。请您提供要翻译的文本(除代码块和 URL 之外的部分),我会按照要求将其翻译成简体中文并保留原有的格式。

Introduction

JWT 默认并不安全。它们快速、无状态且功能强大,但如果不了解实际操作,就很容易被破坏。大多数开发者只是复制粘贴 jwt.verify(token, secret) 然后继续——问题就在这里。

JWT 基础

JWT 代表 JSON Web Token(JSON 网络令牌)。它是 JOSE(JSON 对象签名与加密)的一部分。两个概念很重要:

  • JWS(JSON Web Signature) – 对数据进行签名。任何人都可以读取,但篡改会被检测到。
  • JWE(JSON Web Encryption) – 对数据进行加密。只有预期的接收者才能读取。

大多数 JWT 是 JWS,因为通常不需要隐藏负载,只需要证明它没有被修改。

令牌结构

eyJhbGci... . eyJzdWIi... . 2Xh3n...
│ header │ │ payload │ │ signature │

每一部分都是 base64url 编码的 JSON

部分内容
HEADER{ "alg": "HS256" }
PAYLOAD{ "sub": "user_123" }
SIGNATUREHMAC(header + payload)

可视化分解:

┌─────────────┐
│   HEADER    │ → 算法 + 元数据
├─────────────┤
│   PAYLOAD   │ → 你的声明(可读!)
├─────────────┤
│  SIGNATURE  │ → 完整性证明
└─────────────┘

负载 未加密。Base64 是一种编码方式,而不是加密;任何人都可以解码:

atob("eyJzdWIiOiJ1c2VyXzEyMyJ9"); // {"sub":"user_123"}

标头详情

{
  "alg": "HS256",
  "kid": "f47ac10b-58cc-4372-a567-0e02b2c3d479"
}
  • alg – 用于签名的算法(例如 HS256、RS256、ES256)。
  • kid – 对签署令牌的密钥的标识符(仅限 UUID;绝不使用文件路径或 URL)。
  • jku – 获取密钥的 URL(永远不要信任令牌中的此值)。

规则: 不要基于标头值做安全决策;攻击者可以控制这些值。

负载声明

{
  "sub": "user_5839",
  "exp": 1640995200,
  "iss": "https://auth.yourapp.com",
  "aud": "https://api.yourapp.com",
  "jti": "a1b2c3d4"
}
  • sub – 主体(用户标识)。
  • exp – 过期时间(必填)。
  • iss – 发行者。
  • aud – 受众。
  • jti – 唯一令牌 ID(用于注销/列入黑名单)。

永不在 JWT 中存储机密信息(密码、API 密钥、社会安全号码)。

JWE(加密)

当需要保密时,JWE 会加密载荷:

header.encrypted_key.iv.ciphertext.tag

何时使用 JWE

  • 令牌会经过不受信任的中间方。
  • 合规要求对静止数据进行加密。

何时省略 JWE

  • 通信已使用 HTTPS(在传输中已加密)。
  • 发行者和验证者均在您的控制之下。

大多数系统不需要 JWE。

常见陷阱

算法混淆

// Vulnerable: algorithm not enforced
const decoded = jwt.verify(token, publicKey);

如果令牌声明 "alg": "HS256",但服务器期望使用 RS256,攻击者就可以伪造令牌。

none 算法

"alg": "none" 的令牌没有签名。一些库错误地接受它:

eyJhbGciOiJub25lIn0.eyJzdWIiOiJhZG1pbiJ9.

未验证的 kid

{ "kid": "../../etc/passwd" }
{ "kid": "http://attacker.com/keys" }

直接根据 kid 加载密钥而不进行验证,可能导致任意文件读取或远程密钥注入。

不可信的 jku

如果服务器根据令牌中提供的 URL(jku)去获取密钥,攻击者可以将其指向恶意来源。

验证最佳实践

jwt.verify(token, key, {
  algorithms: ['RS256'],                     // Hard‑code allowed algorithms
  issuer: 'https://auth.yourapp.com',        // Hard‑code expected issuer
  audience: 'https://api.yourapp.com',      // Hard‑code expected audience
  maxAge: '30m'                              // Enforce token age
});
  • 显式设置允许的算法。
  • 验证 iss(发行者)和 aud(受众)。
  • 绝不要信任令牌中的 jku;在服务器端配置 JWKS URL。
  • 使用 UUID 作为 kid,而不是文件路径或 URL。
  • 保持访问令牌的有效期短(15–30 分钟)。
  • 使用带有撤销支持的更长刷新令牌。

摘要

JWT 是一个 签名声明。签名保证了完整性和真实性,但它 并不 保证该令牌应被接受。是否接受取决于对声明(expissaud 等)的正确验证。

大多数 JWT 漏洞来源于:

  • 信任本应不可信的输入(例如,头部字段)。
  • 跳过关键的验证步骤。

严格配置你的验证,验证所有内容,切勿假设库会为你强制安全。了解签名实际能证明的内容,是安全实现与破碎实现之间的区别。

参考文献

  • RFC 7515 – JSON Web Signature (JWS)
  • RFC 7516 – JSON Web Encryption (JWE)
  • RFC 7519 – JSON Web Token (JWT)
Back to Blog

相关文章

阅读更多 »