分布式会话与登录态
在单体应用中,HttpSession 由应用服务器保存,天然可用。但系统拆分为多实例部署(负载均衡)或微服务后,用户的请求会落到不同实例,基于内存的 HttpSession 无法共享,登录态就会丢失。本文系统讲解分布式会话的常见解决方案及取舍。
一、问题的本质
1.1 为什么单体 Session 失效
单体应用只有一个进程,HttpSession 存在进程内存中,同一用户请求必然到达同一进程。但在分布式/微服务下:
用户请求 -> 负载均衡 -> 可能分发到 服务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)将同一用户的请求始终分发到同一实例。
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):
# 引入 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 结构(三部分,用 . 连接):
Header.Payload.Signature- Header:算法与类型信息。
- Payload:声明(claims),如用户 ID、角色、过期时间
exp。 - Signature:对前两部分签名,服务端持有密钥验证,防篡改。
认证流程:
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 流程(简化):
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。
四、安全实践
- Token/Session 过期:合理设置过期时间,长期会话用滑动续期。
- HTTPS 传输:Token、Cookie 必须走 HTTPS,防中间人窃取。
- 防 XSS/CSRF:Cookie 设置
HttpOnly、SameSite,防止被脚本读取和跨站请求伪造。 - 敏感信息不入 Token:JWT Payload 不存明文敏感信息。
- 密钥管理:JWT 签名密钥、会话加密密钥要安全存储并定期轮换。
- 主动失效能力:若需踢人,结合 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 的组件版本即可。