正在加载,请稍候…

JWT 与 Session Token:你应该使用哪种身份验证方法?

JWT 与基于会话的身份验证的实用比较——涵盖各自的工作原理、安全权衡、可扩展性影响以及何时选择一种而非另一种。

JWT 与 Session Token:你应该使用哪种身份验证方法?

两种证明身份的方式

用户使用用户名和密码登录后,服务器需要一种方式来识别后续请求中的该用户——因为 HTTP 是无状态的。每个请求到达时都没有对之前请求的固有记忆。

解决这个问题有两种主流方法:

  1. Session Token——服务器创建登录记录并存储,然后给客户端一个引用键(会话 ID),供其在后续请求中出示。
  2. JWT(JSON Web Token)——服务器创建一个包含用户身份和声明的自包含令牌,签名后交给客户端。无需服务器端存储。

两种方法都可行,但各有不同的权衡。

JWT 与 Session Token:你应该使用哪种身份验证方法?插图

基于会话的身份验证如何工作

  1. 用户向 POST /login 发送凭据
  2. 服务器验证凭据并在存储(数据库、Redis、内存)中创建会话记录
  3. 服务器将会话 ID 作为 cookie 返回(通常为 HttpOnly; Secure; SameSite=Strict
  4. 浏览器自动将 cookie 附加到每个后续请求
  5. 服务器在每个请求上查找存储中的会话 ID 以识别用户
  6. 要注销,服务器删除会话记录——会话 ID 立即失效

会话 ID 本身毫无意义——它是一个随机的不透明字符串,如 sess_a3f9d2c8b4。所有实际用户数据都存储在服务器上。

JWT 身份验证如何工作

  1. 用户向 POST /login 发送凭据
  2. 服务器验证凭据并创建包含声明(用户 ID、角色、过期时间)的 JWT
  3. 服务器使用密钥(HMAC)或私钥(RSA/ECDSA)对 JWT 进行签名
  4. 服务器返回 JWT——客户端将其存储在内存、localStorage 或 cookie 中
  5. 客户端在每个请求的 Authorization: Bearer <token> 头中发送 JWT
  6. 服务器验证签名并读取声明,无需任何存储查找
  7. 要“注销”,客户端丢弃令牌——但令牌在 exp 声明过期之前仍然有效

解码后的 JWT 负载如下所示:

{
  "sub": "user_12345",
  "email": "alice@example.com",
  "roles": ["user", "editor"],
  "iat": 1716800000,
  "exp": 1716886400
}

令牌外部的签名确保此负载未被篡改。

并排比较

属性 Session Token JWT
服务器端存储 需要(DB、Redis) 不需要
即时撤销 是——删除会话 否——必须等待过期
可扩展性 多服务器设置中需要共享存储 无需协调即可在任何服务器上工作
令牌大小 小(随机 ID,约 20–40 字节) 较大(编码后的声明,约 200–600 字节)
客户端可读负载 是(base64 解码,但非秘密)
最佳传输方式 HttpOnly cookie Authorization 头或 HttpOnly cookie
注销效果 完全 仅移除客户端副本
复杂性 实现和推理简单 需要理解 JWT 规范

JWT 的撤销问题

这是最常被误解的权衡。JWT 在过期前一直有效。如果用户账户被入侵,或者管理员需要立即使令牌失效,则没有服务器端记录可删除。

存在解决方案,但每个都会增加复杂性:

短过期时间 + 刷新令牌——签发 15 分钟过期的访问令牌和数天或数周过期的刷新令牌。被入侵的访问令牌很快失效。这是最广泛推荐的模式。

令牌黑名单——维护一个已失效 JWT ID(jti 声明)的数据库。在每个请求上检查它。这又带回了服务器端存储——但现在只存储例外情况,而不是所有会话。

用户记录中的版本字段——在 JWT 中包含一个 token_version 声明。将当前版本存储在用户记录中。在注销或更改密码时递增。拒绝版本较低的令牌。每个请求需要一次数据库读取。

对于即时撤销至关重要的应用(金融服务、安全敏感的管理工具),会话令牌更简单且更可预测。

JWT 与 Session Token:你应该使用哪种身份验证方法?插图

可扩展性论证

JWT 最常被引用的优势是水平可扩展性。使用会话时,每个请求都需要访问会话存储。在多服务器设置中,这意味着要么使用粘性会话(将每个用户路由到同一台服务器),要么使用共享的外部存储(如 Redis)。

使用 JWT,任何服务器都可以验证任何令牌而无需协调——只需检查签名。这确实简化了分布式架构。

但实际差异通常比声称的要小。Redis 每秒处理数百万次查找。对于大多数应用,会话存储并非瓶颈。可扩展性论证在非常大规模或 serverless/边缘计算环境中最为重要,因为在这些环境中全局状态确实难以共享。

安全考虑

JWT 存储

JWT 的存储位置至关重要。

localStorage——实现简单,但易受 XSS 攻击。页面上的任何 JavaScript(包括第三方脚本)都可以读取 localStorage。如果你的应用存在 XSS 漏洞,攻击者会窃取每个用户的令牌。

sessionStorage——与 localStorage 相同的 XSS 风险,但令牌在标签页关闭时被清除。

HttpOnly cookie——浏览器自动处理 cookie,JavaScript 无法读取。这是 Web 应用最安全的选择。结合 SameSite=Strict 或 CSRF 令牌时也能抵抗 CSRF。

内存(JavaScript 变量)——抗 XSS,但令牌在页面重新加载时丢失。与 HttpOnly cookie 中的刷新令牌配合使用效果良好。

建议:对于 Web 应用,将 JWT 存储在 HttpOnly cookie 中。对于需要客户端读取声明的单页应用,将 JWT 存储在内存中,并将刷新令牌存储在 cookie 中。

算法选择

验证 JWT 时始终指定预期的算法。历史上存在一个漏洞,允许攻击者将算法更改为 alg: none,从而完全绕过签名验证。现代 JWT 库如果正确配置,可以处理此问题:

// Node.js (jsonwebtoken)
jwt.verify(token, secret, { algorithms: ['HS256'] }); // 始终指定

// 永远不要这样做——容易受到算法混淆攻击
jwt.verify(token, secret);

JWT 与 Session Token:你应该使用哪种身份验证方法?插图

短过期时间

访问令牌应在 15–60 分钟内过期。刷新令牌在 7–30 天内过期并轮换。长期有效的访问令牌是最常见的 JWT 安全错误。

何时使用会话

  • 传统的服务器渲染 Web 应用(Django、Rails、Laravel)
  • 需要即时注销或账户终止的应用
  • 令牌中的用户数据较大或频繁变化的应用
  • 当简单性和可调试性比无状态可扩展性更重要时

何时使用 JWT

  • 移动应用使用的 API(无自动 cookie 处理)
  • 微服务,其中多个服务需要验证身份而无需访问中央数据库
  • Serverless 和边缘计算环境,其中全局状态成本高昂
  • 单点登录(SSO)场景,其中令牌跨域边界

混合方法

许多生产系统同时使用两者。会话 cookie 处理 Web 浏览器登录。JWT 作为短期访问令牌用于 API 调用,特别是来自移动客户端或第三方集成。刷新令牌——存储在安全的 HttpOnly cookie 中——允许无需重新登录即可透明续期。

这种组合为你提供了服务器端会话管理的主要登录安全性、JWT 的 API 授权无状态可扩展性,以及通过会话/刷新令牌实现的即时撤销。

→ 使用 JWT 解析器 解码和检查任何 JWT 令牌,读取其声明并检查其过期时间——无需密钥。