JWT 安全:最佳实践、常见误区与安全用法
JSON Web Token(JWT)在现代身份认证中无处不在——与之相伴的安全误区也同样常见。本指南讲解 JWT 的工作原理、最常见的漏洞,以及每位开发者都应遵循的最佳实践。
什么是 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)和签名算法(如HS256、RS256)的 JSON。 - ✓负载(Payload):包含声明的 JSON——注册声明(iss、sub、aud、exp、nbf、iat、jti)、公共声明以及应用自定义的私有声明。
- ✓签名(Signature):使用头部声明的算法以及密钥或私钥,对编码后的头部和负载计算出的密码学签名。
重要提示:头部和负载只是经过编码,并非加密。任何截获 JWT 的人都可以解码并读取其内容。机密性绝不能依赖令牌编码——密码、信用卡号或个人敏感信息绝不能放入 JWT 负载。
安全风险:算法混淆与弱密钥
算法混淆攻击利用了信任令牌头部 alg 字段的 JWT 库。如果服务器使用 RS256(非对称、公私钥对)签名,但验证器接受 alg: none 或 HS256,攻击者就可以把服务器的公钥当作 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头部。