Skip to content

JWT 安全:最佳实践、常见误区与安全用法

JSON Web Token(JWT)在现代身份认证中无处不在——与之相伴的安全误区也同样常见。本指南讲解 JWT 的工作原理、最常见的漏洞,以及每位开发者都应遵循的最佳实践。

作者:陈光明更新于 2026-08-01

什么是 JWT?

JWT(JSON Web Token)定义于 RFC 7519,是一种紧凑的、URL 安全的、用于在两方之间传递声明的表示方式。JWT 将一个 JSON 对象编码为三段以 Base64URL 分隔的部分:头部、负载和签名。签名使接收方能够验证令牌未被篡改,因此 JWT 成为 API、单页应用和微服务中无状态认证的热门选择。

与不透明的会话标识符不同,JWT 是自描述的:负载本身携带声明,例如签发者(iss)、主体(sub)、过期时间(exp)以及自定义用户数据。这种自包含设计支持无状态验证——服务端无需在数据库中查询令牌即可验证,只需校验签名密钥。

JWT 结构:Header.Payload.Signature

JWT 由三段 Base64URL 编码的部分组成,以点号分隔(xxxxx.yyyyy.zzzzz):

  • 头部(Header):描述令牌类型(typ: JWT)和签名算法(如 HS256RS256)的 JSON。
  • 负载(Payload):包含声明的 JSON——注册声明(iss、sub、aud、exp、nbf、iat、jti)、公共声明以及应用自定义的私有声明。
  • 签名(Signature):使用头部声明的算法以及密钥或私钥,对编码后的头部和负载计算出的密码学签名。

重要提示:头部和负载只是经过编码,并非加密。任何截获 JWT 的人都可以解码并读取其内容。机密性绝不能依赖令牌编码——密码、信用卡号或个人敏感信息绝不能放入 JWT 负载。

安全风险:算法混淆与弱密钥

算法混淆攻击利用了信任令牌头部 alg 字段的 JWT 库。如果服务器使用 RS256(非对称、公私钥对)签名,但验证器接受 alg: noneHS256,攻击者就可以把服务器的公钥当作 HMAC 密钥重新签名令牌,从而伪造令牌。防御方法很简单:在服务端固定预期算法,并拒绝任何头部声明不同算法的令牌。

弱密钥仍是持久存在的问题。许多团队使用过短或可猜测的 HMAC 密钥,可被离线破解工具用常见密码和字典词对截获的令牌进行暴力破解。HMAC 密钥应当是由密码学随机数生成器产生的长、随机、高熵值——绝不能是项目名、域名或人类可读短语。

安全风险:令牌泄露与过期

令牌泄露仍是 JWT 相关安全事件最常见的成因之一。存储在 localStorage 中的令牌可被页面上运行的任何 JavaScript 读取,包括通过跨站脚本(XSS)注入的恶意脚本。放入 URL 的令牌可能泄露到服务器日志、浏览器历史和 Referer 头中。JWT 一旦被盗,攻击者就能在令牌过期前冒充受害者——而且由于 JWT 是无状态的,服务端很难撤销它。

缺失或过弱的过期机制是另一个常见缺陷。没有 exp 声明,或生命周期过长的 JWT,一旦泄露就等于给了攻击者一份永久凭证。每个 JWT 都应包含 exp 声明,设置较短且合理的生命周期;长期访问应通过可轮换、可撤销的刷新令牌来处理。

安全使用 JWT 的最佳实践

短过期时间配合刷新令牌,在安全性与可用性之间提供了最佳平衡。访问令牌应在几分钟内过期,而非几小时或几天,从而限制令牌被盗后的损失。刷新令牌安全存储(最好放在 HttpOnly、Secure、SameSite Cookie 中),允许用户无需重新登录即可获取新的访问令牌,并可在疑似泄露时在服务端轮换与撤销。

RFC 8725 推荐的额外最佳实践:

  • 始终使用 HTTPS,避免令牌在传输中被截获。
  • 尽可能将令牌存储在 HttpOnly Cookie 中;避免将敏感令牌存入 localStorage
  • 在服务端固定签名算法;绝不信任令牌中的 alg 头部。
  • HMAC 使用强、高熵密钥;条件允许时优先使用非对称密钥(RS256/ES256)。
  • 每次请求都验证所有注册声明(issaudexpnbf)。
  • 为登出和令牌泄露场景实现令牌撤销策略。

对比表:JWT vs Session vs OAuth

方面JWTSessionOAuth
状态模型无状态(自包含)有状态(服务端存储)框架 / 协议
存储位置客户端(Cookie 或存储)服务端会话存储因实现而异(基于令牌)
可扩展性高(无需共享会话存储)较低(需共享存储)
过期机制自包含 exp 声明服务端控制基于令牌,可刷新
撤销能力困难(无状态)简单(删除会话)通过刷新令牌轮换
适用场景API 认证、微服务Web 应用、简单认证第三方委托访问
主要风险令牌泄露、算法混淆会话劫持令牌失窃、重定向攻击

技术参考:RFC 7519

RFC 7519 定义了 JWT 的结构、标准注册声明和序列化规则。JWT 使用的签名算法定义于 RFC 7518(JSON Web Algorithms,JWA),涵盖 HMAC(HS256/HS384/HS512)、RSA(RS256 等)、ECDSA(ES256 等),以及应当始终被禁用的 none 算法。

对于实际部署,RFC 8725:JSON Web Token 最佳当前实践 是必读材料。它总结了多年 JWT 误用的经验教训,涵盖算法固定、密钥管理、声明校验,以及何时根本不应使用 JWT。

  • RFC 7519:定义 JWT 紧凑序列化和注册声明(iss、sub、aud、exp、nbf、iat、jti)。
  • RFC 7518(JWA):定义用于签名和(单独地)加密 JWT 的密码学算法。
  • RFC 8725:最佳当前实践——算法固定、密钥强度、校验,并明确反对 alg: none

常见问题

JWT 默认是加密的吗?

不是。默认情况下,JWT 只是经过 Base64URL 编码并加上了密码学签名——它并未加密。任何获得令牌的人都可以解码并读取头部和负载。如果需要机密性,必须使用 JWE(JSON Web Encryption)或在编码前对负载进行加密。

什么是 JWT 的算法混淆攻击?

算法混淆攻击利用了信任令牌头部 alg 字段的 JWT 库。如果服务器使用 RS256(非对称)但验证器接受 alg: none 或 HS256,攻击者就可以把服务器的公钥当作 HMAC 密钥重新签名令牌。修复方法是在服务端固定预期算法,并拒绝任何头部声明不同算法的令牌。

在浏览器中应该把 JWT 令牌存储在哪里?

建议优先使用 HttpOnly、Secure、SameSite Cookie 来存储令牌,因为 JavaScript 无法读取它们,可防范基于 XSS 的窃取。应避免将敏感令牌存储在 localStorage 或 sessionStorage 中,因为页面上运行的任何 JavaScript(包括注入的恶意脚本)都可以读取它们。

JWT 访问令牌应该存活多长时间?

访问令牌应当短生命周期——通常 5 到 15 分钟。这样一旦令牌泄露,损失也有限。对于更长的会话,应使用带轮换和服务端撤销能力的刷新令牌,让用户无需重新登录即可获取新的访问令牌,同时保持访问令牌生命周期很短。

相关工具