{T}

分布式会话与登录态

在单体应用中,HttpSession 由应用服务器保存,天然可用。但系统拆分为多实例部署(负载均衡)或微服务后,用户的请求会落到不同实例,基于内存的 HttpSession 无法共享,登录态就会丢失。本文系统讲解分布式会话的常见解决方案及取舍。

一、问题的本质

1.1 为什么单体 Session 失效

单体应用只有一个进程,HttpSession 存在进程内存中,同一用户请求必然到达同一进程。但在分布式/微服务下:

text
用户请求 -> 负载均衡 -> 可能分发到 服务A / 服务B / 服务C

如果 Session 只存在服务 A 内存,请求被分发到服务 B 时,就找不到 Session,用户被要求重新登录。

1.2 分布式会话的核心目标

  • 跨实例共享:任意实例都能识别同一用户。
  • 无状态可扩展:水平扩容时登录态不失效。
  • 安全:防伪造、防篡改、可过期。

二、常见解决方案

2.1 Session 复制(Session Replication)

让集群内各实例互相复制 Session(如 Tomcat 的 DeltaManager)。各实例内存中都有全量 Session。

优点:实现简单,应用无感知。 缺点Session 复制有网络开销与延迟,实例越多开销越大;存在内存冗余。仅适合小型集群,不适合大规模。

2.2 粘性会话(Session Sticky)

通过负载均衡器(Nginx 的 ip_hash、基于 Cookie 的 sticky)将同一用户的请求始终分发到同一实例

text
Nginx: upstream xxx { ip_hash; server A; server B; }

优点:实现简单,无需改动应用。 缺点一旦该实例宕机,该实例上的所有用户 Session 全部丢失;扩容/缩容会导致 session 重新分布(用户被登出)。可用性差,不推荐作为主方案。

2.3 Redis 集中式 Session(主流方案)

把 Session 存到 Redis,所有实例共享同一份 Session 数据。应用通过 Spring Session 等框架透明地切换到 Redis 存储。

优点

  • 真正无状态:实例不保存 Session,水平扩容/缩容不影响。
  • 高可用:Redis 集群(主从/哨兵)保障。
  • 可控制过期:利用 Redis TTL 控制 Session 有效期。

实现(Spring Session + Redis):

yaml
# 引入 spring-session-data-redis 后,默认将 HttpSession 存到 Redis
spring:
  session:
    store-type: redis
    timeout: 30m

注意:Session 数据要序列化(JSON/Java 序列化),并避免把大对象放进 Session(Redis 内存成本高)。

2.4 基于 Token 的无状态认证(JWT)

不使用服务端 Session,而是把登录态信息放进客户端持有的 Token(最常用 JWT),服务端通过验签即可识别用户,无需存储任何会话状态。

JWT 结构(三部分,用 . 连接):

text
Header.Payload.Signature
  • Header:算法与类型信息。
  • Payload:声明(claims),如用户 ID、角色、过期时间 exp
  • Signature:对前两部分签名,服务端持有密钥验证,防篡改。

认证流程

text
1. 用户登录 -> 服务端签发 JWT(含用户ID、过期时间)返回客户端
2. 客户端后续请求携带 JWT(Authorization: Bearer <token>)
3. 服务端验证签名和过期时间 -> 从 Payload 解析用户身份

优点

  • 完全无状态:服务端不存会话,天然适合分布式/微服务。
  • 跨服务、跨端共享方便。
  • 适合移动端、多端登录。

缺点与注意

  • 无法主动失效:已签发的 JWT 在过期前无法强制踢下线(需借助黑名单/版本号)。
  • Payload 不加密:只做签名,敏感信息不要放 Payload(或加密)。
  • 密钥管理:签名密钥要安全保管、支持轮换。

适用场景:适合接口鉴权、移动端、前后端分离;若需要"主动踢人""服务端可控会话",可结合 Redis 存储 JWT 或改用 Redis Session。

2.5 单点登录(SSO)

当系统有多个独立应用(如门户、后台、商城),用户登录一个应用后,希望访问其他应用无需重复登录。SSO 通过**认证中心(SSO Server)**统一管理登录态。

常见实现

  • 基于 Cookie 的共享域名 SSO:统一域名下共享登录 Cookie(简单但跨域受限)。
  • CAS(Central Authentication Service):经典 SSO 协议,认证中心签发票据(Ticket),各应用凭票据换取用户信息。
  • OAuth2 / OIDC:现代 SSO 方案,通过授权服务器签发 Token,适用于开放平台与第三方登录。

CAS 流程(简化)

text
1. 用户访问 应用A,未登录 -> 重定向到 认证中心
2. 认证中心校验登录(用户名/密码),生成 TGT 票据存认证中心,设置全局登录 Cookie
3. 认证中心生成 ST 服务票据,重定向回 应用A
4. 应用A 用 ST 向认证中心校验,换取用户信息,建立本地会话
5. 用户访问 应用B,同样重定向认证中心,认证中心发现已全局登录,直接签发 ST -> 免登录

优点:一处登录,处处可用,用户体验好;集中管理认证与安全。 缺点:认证中心是单点,需高可用部署;协议实现较复杂。

三、方案对比与选型

方案无状态可扩展高可用复杂度适用场景
Session 复制×小型集群
粘性会话×简单过渡方案
Redis Session主流的 Web 应用分布式会话
JWT√√最好前后端分离、移动端、微服务鉴权
SSO(CAS/OAuth2)视实现需高可用多应用统一登录

选型建议

  • 传统 Web(服务端渲染)多实例 → Redis Session(Spring Session)。
  • 前后端分离/移动端/微服务 API → JWT。
  • 多个独立系统需统一登录 → SSO(CAS 或 OAuth2/OIDC)。
  • 大型系统常组合使用:内部微服务用 JWT 做服务间鉴权,用户入口用 SSO 统一登录,会话细节存 Redis。

四、安全实践

  1. Token/Session 过期:合理设置过期时间,长期会话用滑动续期。
  2. HTTPS 传输:Token、Cookie 必须走 HTTPS,防中间人窃取。
  3. 防 XSS/CSRF:Cookie 设置 HttpOnlySameSite,防止被脚本读取和跨站请求伪造。
  4. 敏感信息不入 Token:JWT Payload 不存明文敏感信息。
  5. 密钥管理:JWT 签名密钥、会话加密密钥要安全存储并定期轮换。
  6. 主动失效能力:若需踢人,结合 Redis 黑名单或版本号控制 JWT 有效性。

小结

  • 单体 Session 在多实例/微服务下失效,核心是会话状态要共享或下沉
  • Redis Session 适合传统 Web 多实例;JWT 适合前后端分离与微服务鉴权;SSO 解决多系统统一登录。
  • 无论哪种方案,都要关注过期、HTTPS、防 XSS/CSRF、密钥安全

版本差异(技术原理说明)

维度说明
技术原理分布式一致性/事务/锁/ID 生成等原理与具体版本无关,长期有效
落地选型新项目建议优先使用 Nacos/Redis/Seata 等成熟组件(JDK 17+ 兼容)
Java 版本示例代码基于 JDK 8 编写,JDK 17/21 下语法兼容

本文讲解的分布式系统核心问题与解决方案原理稳定,不随框架版本变化;落地时选用支持 JDK 17/21 的组件版本即可。