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 启动就能直接处理请求扩容就像复制文件一样简单。// 用 Session 时每次请求都要查 Redisapp.get(/api/user,async(req,res){constsessionIdreq.cookies.sessionId;constuserawaitredis.get(session:${sessionId});// 网络IO// ...});// 用 JWT 时本地验签即完成认证app.get(/api/user,async(req,res){consttokenreq.headers.authorization;constuserjwt.verify(token,SECRET);// 本地计算无网络IO// ...});2. 跨端能力天生强大我们的业务场景涉及Web 端浏览器微信小程序移动 App后续规划Session 依赖 Cookie在小程序和 App 里处理 Cookie 非常别扭需要手动维护、注意跨域、还要处理移动端的 Cookie 限制。JWT 就简单多了任何端只要能发 HTTP 请求把 Token 放在 Header 里就行。小程序、App、甚至物联网设备都能统一接入。3. 减少数据库查询压力传统 Session 方案里为了获取用户信息比如昵称、头像、权限每个请求可能要根据 SessionId 查 Redis 拿到 userId再查数据库或缓存获取用户详情而 JWT 可以在签发时直接把非敏感的用户信息编码进去consttokenjwt.sign({userId:123456,nickname:张三,role:vip,exp:Math.floor(Date.now()/1000)7200// 2小时过期},SECRET);后续请求验证通过后直接取token.userId、token.role使用数据库零查询。这对高并发场景非常友好。4. 技术栈无关微服务友好我们的后端有 Node.js 的 BFF 层也有 Java 的业务服务。如果使用 SessionJava 和 Node 要共享同一个 Session 存储维护一套序列化协议很麻烦。JWT 只要共享一个密钥或 RSA 公钥任何语言都能验证// Java 端验证同一个 TokenStringtokenrequest.getHeader(Authorization);ClaimsclaimsJwts.parser().setSigningKey(SECRET).parseClaimsJws(token).getBody();LonguserIdclaims.get(userId,Long.class);不需要额外的存储中间件不需要考虑序列化兼容性。三、但我必须承认JWT 不是银弹选 JWT 的同时我也接受了它的三个代价问题1无法主动失效用户修改密码后旧的 JWT 在有效期内依然能用。被封号的用户只要 Token 没过期还能继续请求。我的解决方案短过期时间2小时 Refresh Token 机制敏感操作修改密码、注销时客户端主动清除本地 Token黑名单机制只针对极端情况如封号// 封号时加入黑名单Redisawaitredis.setex(blacklist:${token},tokenExp,1);// 验证时检查黑名单if(awaitredis.exists(blacklist:${token})){returnres.status(401).send(Token invalidated);}问题2Token 体积较大SessionId 只有几十字节JWT 轻松几百字节尤其是存了用户信息后。影响每次请求的 Header 多几百字节高频 API 会多消耗流量。权衡现代网络带宽下这点开销可以接受。我们做了 Gzip 压缩实测影响 5%。问题3安全性依赖 HTTPSJWT 明文存储即使签名防篡改但内容 base64 解码后可见绝对不能存密码等敏感信息。我的做法强制 HTTPS敏感信息如支付密码永远不走 JWT 传输JWT 只存 userId、role 这种非敏感标识四、最终架构JWT Refresh Token 双 Token 机制实际落地的方案是经典的双 Token模式Access TokenJWT - 有效期 2 小时用于业务 API 调用 Refresh Token随机字符串 - 有效期 7 天存在 Redis用于换取新 Access Token流程登录成功 → 返回 Access Token Refresh TokenAccess Token 过期 → 用 Refresh Token 换取新 TokenRefresh 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 就挺好。希望这篇文章对你有帮助。如果你也在做类似选型欢迎在评论区交流你的思考