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.userIdtoken.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 就挺好。


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

相关推荐
庞轩px1 天前
JWT + Redis 双 Token 机制:从原理到实战
数据库·redis·缓存·jwt·token·登录认证
带娃的IT创业者2 天前
WeClaw_43_双重认证与Token自动刷新:Device Fingerprint与JWT安全机制
jwt·认证机制·双重认证·设备指纹·token刷新·http安全
happymaker06264 天前
深入了解会话跟踪技术、过滤器、EL、JSTL
web开发·token·cookie·session·el·jstl
七夜zippoe16 天前
OpenClaw 核心概念详解:Session、Agent、Skill
agent·session·skill·分层架构·openclaw
૮・ﻌ・16 天前
Node.js - 04:MongoDB、会话控制
数据库·mongodb·node.js·jwt·token·cookie·session
indexsunny16 天前
互联网大厂Java面试实战:从Spring Boot到微服务架构的音视频场景解析
java·spring boot·spring cloud·mybatis·spring security·jwt·flyway
没有bug.的程序员17 天前
撕裂微服务网关的认证风暴:Spring Security 6.1 与 JWT 物理级免登架构大重构
java·spring·微服务·架构·security·jwt
Zacks_xdc17 天前
【全栈】Next.js + PostgreSQL + Vercel 实现完整登录系统(完整源码)
postgresql·全栈·next.js·登录鉴权·vercel
独断万古他化18 天前
【抽奖系统开发实战】Spring Boot 项目的用户模块设计:注册登录、权限管控与敏感数据加密
java·spring boot·redis·后端·mvc·jwt·拦截器