JWT与Session比较

前言

复制代码
在设想架构时,我面临了一个经典的选择:用户登录后的凭证管理,到底是用传统的 Session,还是用这几年特别火的 JWT?

在做了充分的技术调研和压测后,我最终选择了 JWT。今天这篇文章,我想把当时的思考过程、踩过的坑以及最终的决策逻辑分享出来,希望能给正在做类似选型的你一些参考。

一、先简单回顾一下:Session 和 JWT 分别是怎么工作的?

Session 模式(传统方案)

复制代码
用户登录 → 服务端生成 SessionId → 存入 Redis/内存 → 返回给客户端 Cookie
后续请求 → Cookie 带 SessionId → 服务端查存储 → 拿到用户信息

特点:服务端存状态,客户端只存一个标识。

JWT 模式(无状态方案)

复制代码
用户登录 → 服务端生成 JWT(内含用户信息+签名) → 返回给客户端
后续请求 → 请求头带 JWT → 服务端验签名 → 直接读取用户信息

特点:服务端无状态,客户端存完整凭证。

二、最终让我选择 JWT 的 4 个核心理由

1. 服务无状态 → 水平扩展零成本

我们的项目部署在 Kubernetes 上,Pod 数量会随着流量自动伸缩。

如果使用 Session,我必须维护一个共享的 Session 存储(比如 Redis 集群)。每个请求进来,服务端都要去 Redis 查一次。Pod 越多,Redis 的压力越大,网络开销也越大。

而用 JWT,每个 Pod 都能独立验证 Token,完全不需要任何共享存储。新 Pod 启动就能直接处理请求,扩容就像复制文件一样简单。

javascript 复制代码
// 用 Session 时:每次请求都要查 Redis
app.get('/api/user', async (req, res) => {
  const sessionId = req.cookies.sessionId;
  const user = await redis.get(`session:${sessionId}`); // 网络IO
  // ...
});

// 用 JWT 时:本地验签即完成认证
app.get('/api/user', async (req, res) => {
  const token = req.headers.authorization;
  const user = jwt.verify(token, SECRET); // 本地计算,无网络IO
  // ...
});

2. 跨端能力天生强大

我们的业务场景涉及:

  • Web 端(浏览器)
  • 微信小程序
  • 移动 App(后续规划)

Session 依赖 Cookie,在小程序和 App 里处理 Cookie 非常别扭(需要手动维护、注意跨域、还要处理移动端的 Cookie 限制)。

JWT 就简单多了:任何端只要能发 HTTP 请求,把 Token 放在 Header 里就行。小程序、App、甚至物联网设备都能统一接入。

3. 减少数据库查询压力

传统 Session 方案里,为了获取用户信息(比如昵称、头像、权限),每个请求可能要:

  1. 根据 SessionId 查 Redis 拿到 userId
  2. 再查数据库或缓存获取用户详情

而 JWT 可以在签发时,直接把非敏感的用户信息编码进去:

javascript 复制代码
const token = jwt.sign(
  {
    userId: 123456,
    nickname: "张三",
    role: "vip",
    exp: Math.floor(Date.now() / 1000) + 7200  // 2小时过期
  },
  SECRET
);

后续请求验证通过后,直接取 token.userId、token.role 使用,数据库零查询。这对高并发场景非常友好。

4. 技术栈无关,微服务友好

我们的后端有 Node.js 的 BFF 层,也有 Java 的业务服务。如果使用 Session,Java 和 Node 要共享同一个 Session 存储,维护一套序列化协议,很麻烦。

JWT 只要共享一个密钥(或 RSA 公钥),任何语言都能验证:

java 复制代码
// Java 端验证同一个 Token
String token = request.getHeader("Authorization");
Claims claims = Jwts.parser().setSigningKey(SECRET).parseClaimsJws(token).getBody();
Long userId = claims.get("userId", Long.class);

不需要额外的存储中间件,不需要考虑序列化兼容性。

三、但我必须承认:JWT 不是银弹

选 JWT 的同时,我也接受了它的三个代价:

问题1:无法主动失效

用户修改密码后,旧的 JWT 在有效期内依然能用。被封号的用户,只要 Token 没过期,还能继续请求。

我的解决方案:

  • 短过期时间(2小时)+ Refresh Token 机制
  • 敏感操作(修改密码、注销)时,客户端主动清除本地 Token
  • 黑名单机制(只针对极端情况,如封号)
javascript 复制代码
// 封号时加入黑名单(Redis)
await redis.setex(`blacklist:${token}`, tokenExp, '1');
// 验证时检查黑名单
if (await redis.exists(`blacklist:${token}`)) {
  return res.status(401).send('Token invalidated');
}

问题2:Token 体积较大

SessionId 只有几十字节,JWT 轻松几百字节(尤其是存了用户信息后)。

影响:每次请求的 Header 多几百字节,高频 API 会多消耗流量。

权衡:现代网络带宽下,这点开销可以接受。我们做了 Gzip 压缩,实测影响 <5%。

问题3:安全性依赖 HTTPS

JWT 明文存储(即使签名防篡改,但内容 base64 解码后可见),绝对不能存密码等敏感信息。

我的做法:

  • 强制 HTTPS
  • 敏感信息(如支付密码)永远不走 JWT 传输
  • JWT 只存 userId、role 这种非敏感标识

四、最终架构:JWT + Refresh Token 双 Token 机制

实际落地的方案是经典的双 Token模式:

复制代码
Access Token(JWT)   - 有效期 2 小时,用于业务 API 调用
Refresh Token(随机字符串) - 有效期 7 天,存在 Redis,用于换取新 Access Token

流程:

  1. 登录成功 → 返回 Access Token + Refresh Token
  2. Access Token 过期 → 用 Refresh Token 换取新 Token
  3. Refresh Token 过期或被盗 → 用户重新登录

这样既享受了 JWT 的无状态便利,又通过 Refresh Token 实现了可控的吊销能力。

五、什么情况我建议你选 Session?

虽然我选了 JWT,但以下场景 Session 可能更合适:

场景 原因
单体应用 + 服务端渲染 Session 原生支持,简单可靠
需要实时踢人下线的管理系统 Session 可随时删除,JWT 很难
用户量不大(<1万并发) Session + Redis 足够,无需折腾
银行、支付等安全敏感系统 Session 可控性更强
团队对 JWT 不熟悉 避免引入不熟悉的技术

六、写在最后

技术选型没有绝对的对错,只有适不适合。

JWT 用无状态换来了扩展性和跨端能力,代价是放弃了主动失效的便利。如果你的项目需要快速水平扩展、服务多端、追求性能,JWT 是一个非常好的选择。如果你的项目重视实时管控、用户量不大、单体架构,Session 依然简单好用。

我的一条建议:不要为了用 JWT 而用 JWT。先问自己几个问题:

  • 你的服务需要水平扩展吗?
  • 你有多个端(Web/App/小程序)吗?
  • 你能接受 Token 无法主动失效吗?

答案如果偏"是",JWT 值得一试;如果偏"否",Session 就挺好。


希望这篇文章对你有帮助。如果你也在做类似选型,欢迎在评论区交流你的思考!

相关推荐
我不会起名字3222 天前
Redis 从 0 到 1(十九):JWT 还是 Redis Session?登录态存储的 Go 实现
redis·go·jwt·session·登录态
Thanks_ks3 天前
Cookie 和 Session 到底是什么关系——登录态是怎么维持的
分布式·http·jwt·cookie·session·后端开发·登录态
流浪0015 天前
计算机网络篇13:应用层——HTTP 不记录用户状态,Cookie 在客户端存凭证、Session 在服务端存数据
计算机网络·cookie·session·http协议
枫子有风6 天前
卡码网盘项目深挖
java·redis·jwt·es·pgvector
FungLeo7 天前
成为全栈·Go 后端篇·跨语言认证兼容:Node 与 Go 如何互认 JWT 和密码
golang·go·jwt·身份认证·成为全栈·bcrypt
酣大智8 天前
hibernate学习笔记
java·hibernate·session
j7~12 天前
【Linux网络加餐】《HTTP 会话保持:Cookie 与 Session 原理及实验验证》---详解
linux·网络·网络协议·http·cookie·session
AIGC大时代12 天前
OpenAI Agents SDK 工程笔记:自定义 Session 接口(get_items / add_items / clear)与多租户隔离的生产禁区
session·多租户·agents sdk·生产禁区·get_items
积流成海16 天前
JWT 解析:三段结构、过期时间换算与常见误区
jwt·盘子工具站
承渊政道16 天前
Linux网络学习【深入理解应用层HTTP协议、Cookie与Session机制及HTTPS协议原理】
linux·网络·学习·http·cookie·session