一句话总结
分布式Session方案对比:Session粘滞(Nginx ip_hash,简单但负载不均)→ Session复制(Tomcat集群广播,性能差)→ 集中存储(Redis + Spring Session,最常用)→ JWT无状态(客户端存储,彻底无状态,但无法主动失效)。生产推荐:Redis集中存储(Spring Session)或 JWT(微服务场景)。核心区别:Session是有状态的(服务端存),JWT是无状态的(客户端存)。
初级理解
为什么需要分布式Session?
# 问题:单体 → 分布式
# 单体应用:Session 存在本地内存,没问题
# 分布式:请求被负载均衡到不同服务器
# 用户登录在 Server A,下次请求到 Server B → 未登录!
# 解决方案
# 1. Session 粘滞:同一用户始终路由到同一台服务器
# 2. Session 复制:服务器间同步 Session
# 3. 集中存储:Session 存 Redis/DB
# 4. 无状态:JWT Token,客户端存储
四种方案对比
| 方案 | 原理 | 优点 | 缺点 |
|---|---|---|---|
| Session粘滞 | Nginx ip_hash 固定路由 | 简单,不改代码 | 负载不均,服务器宕机丢失 |
| Session复制 | Tomcat 集群广播 | 不依赖外部存储 | 性能差,网络开销大 |
| 集中存储 | Redis 存 Session | 可靠,性能好 | 依赖 Redis |
| JWT | 客户端存 Token | 无状态,易扩展 | 无法主动失效 |
一句话总结:Session粘滞简单但不可靠;集中存储(Redis)最常用;JWT适合微服务。
中级深入
Redis 集中存储 — Spring Session
# Spring Session + Redis 配置
spring:
session:
store-type: redis
timeout: 1800 # 30 分钟过期
redis:
host: localhost
port: 6379
# 使用(和普通 Session 一样)
@RestController
public class UserController {
@GetMapping("/login")
public String login(HttpSession session) {
session.setAttribute("userId", 1001);
return "ok";
}
@GetMapping("/info")
public String info(HttpSession session) {
Long userId = (Long) session.getAttribute("userId");
return "userId: " + userId;
}
}
# Spring Session 原理
# 1. SessionRepositoryFilter 拦截请求
# 2. 包装 HttpServletRequest → SessionRepositoryRequestWrapper
# 3. getSession() 从 Redis 读写 Session
# 4. 每次请求结束自动刷新 Redis 中的 Session
JWT 无状态方案
# JWT 结构:Header.Payload.Signature
# Header: {"alg": "HS256", "typ": "JWT"}
# Payload: {"userId": 1001, "exp": 1700000000}
# Signature: HMACSHA256(base64(header) + "." + base64(payload), secret)
# 生成 JWT
String jwt = Jwts.builder()
.setSubject("1001") # userId
.setExpiration(new Date(...)) # 过期时间
.signWith(SignatureAlgorithm.HS256, secretKey)
.compact();
# 验证 JWT
Claims claims = Jwts.parser()
.setSigningKey(secretKey)
.parseClaimsJws(jwt)
.getBody();
String userId = claims.getSubject();
# JWT 优缺点
# 优点:无状态、易扩展、跨域友好
# 缺点:无法主动失效(只能等过期)、payload 不能太大
中级要点:Spring Session 自动管理 Redis Session;JWT 无状态但无法主动失效。
高级拓展
Session 共享的安全问题
# 1. Session 固定攻击
# 攻击者获取一个 Session ID → 诱导用户使用该 ID 登录
# 解决:登录成功后重新生成 Session ID
session.invalidate();
session = request.getSession(true);
# 2. Session 劫持
# 攻击者窃取 Session ID(XSS、网络嗅探)
# 解决:HTTPS + HttpOnly Cookie + Secure Cookie
# 3. CSRF 跨站请求伪造
# 攻击者诱导用户点击链接,利用已登录 Session
# 解决:CSRF Token + SameSite Cookie
# 4. Redis 集中存储的安全
# Redis 设置密码 + 内网访问
# Session 数据加密存储(敏感信息)
JWT 主动失效方案
# JWT 无法主动失效的解决方案
# 方案1:黑名单(Redis)
# 需要失效时,将 JWT 加入 Redis 黑名单
# 验证时先查黑名单
SET "jwt:blacklist:xxx" 1 EX 3600
# 方案2:版本号
# 用户表加 token_version 字段
# 修改密码/登出时 version++
# JWT payload 中包含 version
# 验证时比较 JWT version 和 DB version
# 方案3:短有效期 + Refresh Token
# Access Token:15 分钟(短)
# Refresh Token:7 天(长,存 Redis)
# Access Token 过期后用 Refresh Token 刷新
实战场景
场景:微服务认证方案
# 推荐方案:JWT + Redis 黑名单
# 1. 登录 → 返回 Access Token(15min)+ Refresh Token(7天)
# 2. 请求携带 Access Token
# 3. 网关验证 JWT + 查黑名单
# 4. Access Token 过期 → 用 Refresh Token 刷新
# 5. 登出 → Access Token 加入黑名单
# 网关验证拦截器
public class AuthInterceptor {
public boolean preHandle(HttpServletRequest request) {
String token = request.getHeader("Authorization");
# 1. 验证 JWT 签名和过期
Claims claims = JwtUtil.parse(token);
# 2. 查黑名单
if (redis.hasKey("jwt:blacklist:" + token)) {
return false;
}
# 3. 设置用户上下文
UserContext.set(claims.getSubject());
return true;
}
}
面试模拟
面试官:分布式Session怎么解决?
你:四种方案:1)Session粘滞(Nginx ip_hash),简单但负载不均;2)Session复制(Tomcat集群),性能差;3)集中存储(Redis + Spring Session),最常用;4)JWT无状态,适合微服务。推荐Redis集中存储或JWT。
面试官:JWT 怎么实现主动失效?
你:JWT本身无法主动失效,但可以通过以下方式实现:1)Redis黑名单:需要失效时加入黑名单,验证时先查黑名单;2)版本号:用户表加token_version,修改密码时递增;3)短有效期+Refresh Token:Access Token 15分钟,Refresh Token存Redis可主动删除。