Spring Security 的会话管理有哪些?并发会话控制、会话固定攻击防护是什么?
简化版
Spring Security 的会话管理(Session Management)控制「用户的会话怎么创建、能开几个、怎么防攻击」,主要有几块:① 会话创建策略(SessionCreationPolicy)——控制什么时候创建 Session:ALWAYS(总是创建)、IF_REQUIRED(需要才创建,默认)、NEVER(不主动创建但有就用)、STATELESS(完全不用 Session,JWT 场景用这个);② 并发会话控制(Concurrent Session)——限制「一个账号同时能登录几个」(如只允许一个设备在线,新登录踢掉旧的,或阻止新登录),防止账号被多处共用;③ 会话固定攻击防护(Session Fixation)——防止攻击者「用一个已知的 Session ID 骗用户登录、然后用这个 ID 冒充用户」,防护办法是登录成功后更换新的 Session ID(Spring Security 默认开启 changeSessionId);④ 会话超时——Session 过期时间、过期后的处理。核心:会话管理管「Session 怎么建、能开几个、防固定攻击」;JWT 场景用 STATELESS(不用 Session);并发控制限制多端登录;会话固定防护靠登录后换新 Session ID。
详细版
会话创建策略:
| 策略 | 行为 | 场景 |
|---|---|---|
| ALWAYS | 总是创建 Session | 很少用 |
| IF_REQUIRED | 需要时创建(默认) | 传统 Session 登录 |
| NEVER | 不主动创建,有就用 | 少见 |
| STATELESS | 完全不用 Session | JWT / REST API |
// 会话管理配置
http.sessionManagement(session -> session
// ① 会话创建策略(JWT 用 STATELESS)
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
// ② 并发会话控制(只允许一个会话,新登录踢旧的)
.maximumSessions(1) // 最多 1 个会话
.maxSessionsPreventsLogin(false) // false=新登录踢旧的;true=阻止新登录
.expiredUrl("/login?expired") // 会话被踢后跳转
// ③ 会话固定攻击防护(默认 changeSessionId)
.sessionFixation(fixation -> fixation.changeSessionId())
);
会话固定攻击:
攻击者拿到一个 Session ID(如诱导用户用他给的 URL 带 sessionid)
→ 用户用这个 Session ID 登录了
→ 攻击者用同一个 Session ID 冒充用户(因为登录前后 Session ID 没变)
防护:登录成功后换新 Session ID → 攻击者手里的旧 ID 失效
⚠️ 会话管理的两个安全重点是「并发会话控制」和「会话固定攻击防护」,容易被忽视。会话固定攻击(Session Fixation)的原理是:攻击者先获取一个合法的 Session ID,然后诱导受害者用这个 Session ID 去登录(比如给受害者一个带
jsessionid的链接),如果登录前后 Session ID 不变,受害者登录成功后,攻击者就能用同一个 Session ID 冒充已登录的受害者。防护很简单——登录成功后立即更换一个新的 Session ID(Spring Security 默认用changeSessionId策略),这样攻击者手里的旧 Session ID 就作废了。并发会话控制则解决「一个账号被多人共用」的问题:设maximumSessions(1)就是「一个账号同一时间只能一个会话在线」,可以选择「新登录踢掉旧的」(maxSessionsPreventsLogin=false)或「阻止新登录」(=true)。注意:并发会话控制依赖服务端存 Session(要跟踪一个用户有几个会话),所以 STATELESS(JWT)场景下这个功能失效——JWT 无状态,服务端不存会话,没法统计「一个用户几个会话」。
完整版教学
一、会话创建策略
先理解「什么时候创建 Session」的策略:
SessionCreationPolicy(会话创建策略):
① ALWAYS:总是创建 Session(即使不需要)
→ 很少用(浪费)
② IF_REQUIRED(默认):需要时才创建
→ 传统 Session 登录用这个(登录成功创建 Session 存认证)
③ NEVER:Spring Security 不主动创建,但如果已存在就用
→ 少见
④ STATELESS:完全不用 Session
→ 不创建、不使用 Session
→ 每个请求都要重新认证(如每次带 JWT)
→ JWT / REST API 场景用这个
STATELESS 的意义(JWT 场景):
JWT 是无状态的——认证信息在 Token 里,服务端不存 Session
→ 用 STATELESS,Spring Security 不创建 Session
→ 每个请求靠 JWT 认证(无状态、易水平扩展)
配置:
http.sessionManagement(s -> s.sessionCreationPolicy(STATELESS));
选择:
传统 Session 登录 → IF_REQUIRED(默认)
JWT / REST API → STATELESS(不用 Session)
会话创建策略(SessionCreationPolicy):ALWAYS(总是创建,少用)、IF_REQUIRED(需要时创建,默认,传统 Session 登录用)、NEVER(不主动创建有就用,少见)、STATELESS(完全不用 Session,JWT/REST API 用)。STATELESS 的意义:JWT 无状态(认证信息在 Token、服务端不存 Session),用 STATELESS 不创建 Session、每个请求靠 JWT 认证(易水平扩展)。选择:传统 Session 用 IF_REQUIRED、JWT 用 STATELESS。理解「会话创建策略:ALWAYS/IF_REQUIRED(默认)/NEVER/STATELESS(JWT 不用 Session);STATELESS 用于 JWT(无状态每请求靠 Token);传统用 IF_REQUIRED、JWT 用 STATELESS」,就掌握了会话创建策略。
二、并发会话控制
并发会话控制——限制一个账号能开几个会话:
并发会话控制(Concurrent Session Control):
限制"一个账号同时能有几个活跃会话"
maximumSessions(n):最多 n 个会话
场景:
① 只允许一个设备在线(maximumSessions(1))
→ 防止账号共用、防止账号被盗后同时使用
② 允许有限个设备(如 maximumSessions(3))
超过限制时的两种策略:
maxSessionsPreventsLogin(false)(默认):
→ 新登录成功,踢掉最旧的会话
→ "顶号"效果(新设备登录,旧设备被下线)
maxSessionsPreventsLogin(true):
→ 阻止新登录(已达上限,新的登不进来)
→ "先到先得"(旧的还在,新的登不了)
实现依赖(重要):
并发会话控制要"跟踪一个用户有几个会话"
→ 需要服务端存储会话信息(SessionRegistry)
→ 单机:内存 SessionRegistry
→ 集群:需要共享的 SessionRegistry(如 Spring Session + Redis)
★ STATELESS(JWT)场景下失效:
JWT 无状态、服务端不存 Session
→ 没法统计"一个用户几个会话"
→ 并发会话控制这个功能用不了
→ JWT 要控制多端登录,得自己实现(如 Redis 存 user→tokens)
配置:
http.sessionManagement(s -> s
.maximumSessions(1)
.maxSessionsPreventsLogin(false)); // 新登录踢旧的
并发会话控制——限制一个账号的活跃会话数(maximumSessions(n))。场景:只允许一个设备在线(防账号共用/被盗同时用)。超过限制两种策略:maxSessionsPreventsLogin(false)(默认)新登录踢最旧的(顶号)、=true 阻止新登录(先到先得)。实现依赖:要跟踪用户会话数、需服务端存储(SessionRegistry,集群要 Spring Session+Redis)。STATELESS(JWT)场景失效(无状态不存 Session、没法统计会话数,JWT 控多端要自己实现如 Redis 存 user→tokens)。理解「并发会话控制 maximumSessions(n)限制账号会话数、超限策略:false 踢旧的(顶号)/true 阻止新登录;依赖服务端存 Session(SessionRegistry,集群用 Redis);★STATELESS(JWT)失效(不存 Session 没法统计,要自己实现)」,就掌握了并发会话控制。
三、会话固定攻击防护
会话固定攻击(Session Fixation)——理解攻击和防护:
会话固定攻击(Session Fixation):
攻击原理:
1. 攻击者先获取一个合法的 Session ID(访问网站拿一个)
2. 诱导受害者用这个 Session ID(如给一个带 jsessionid 的链接)
3. 受害者用这个 Session ID 登录了
4. ★ 如果登录前后 Session ID 不变:
→ 攻击者用同一个 Session ID → 冒充已登录的受害者!
→ 核心漏洞:登录前后 Session ID 没变,攻击者预先知道这个 ID
防护(登录后换 Session ID):
登录成功后,立即更换一个新的 Session ID
→ 攻击者手里的旧 Session ID 作废(登录后的会话用新 ID)
→ 攻击者拿旧 ID 冒充不了
Spring Security 的防护策略(sessionFixation):
① changeSessionId(默认,Servlet 3.1+):
换一个新 Session ID,保留 Session 属性
② newSession:
创建全新 Session(不保留旧属性)
③ migrateSession:
创建新 Session 并迁移旧属性(旧版默认)
④ none:不防护(危险,别用)
默认开启:
Spring Security 默认用 changeSessionId → 已经防护了
→ 一般不用手动配(除非要改策略)
配置:
http.sessionManagement(s -> s.sessionFixation(f -> f.changeSessionId()));
所以会话固定攻击防护 = 登录后换 Session ID(Spring Security 默认开)
会话固定攻击(Session Fixation):攻击原理——攻击者先获取一个合法 Session ID、诱导受害者用它登录、如果登录前后 Session ID 不变,攻击者就能用同一个 ID 冒充已登录的受害者(核心漏洞是登录前后 ID 没变、攻击者预先知道)。防护——登录成功后立即换新 Session ID(旧 ID 作废、攻击者冒充不了)。Spring Security 策略:changeSessionId(默认,换新 ID 保留属性)、newSession、migrateSession、none(危险)。默认开启 changeSessionId(一般不用手动配)。理解「会话固定攻击:攻击者先拿 Session ID 诱导受害者用它登录、登录前后 ID 不变则攻击者冒充;防护:登录后换新 Session ID(旧 ID 作废);Spring Security 默认 changeSessionId 已防护」,就掌握了会话固定攻击防护。
四、会话超时与失效
会话超时——Session 过期时间和失效处理:
会话超时(Session Timeout):
Session 有过期时间——一段时间不活动就过期
配置(Spring Boot):
server.servlet.session.timeout=30m // 30 分钟
会话失效的处理:
① 会话过期后再访问 → 未认证 → 触发认证流程(去登录)
② 被并发控制踢掉 → expiredUrl 跳转 / 返回提示
会话失效的监听:
HttpSessionEventPublisher:发布会话创建/销毁事件
→ 并发会话控制需要它(跟踪会话)
→ 配置:注册 HttpSessionEventPublisher Bean
失效相关配置:
invalidSessionUrl:Session 无效时跳转
expiredUrl:Session 被并发控制踢掉时跳转
maximumSessions + expiredUrl:会话被顶掉后的处理
前后端分离的会话失效:
返回 JSON(而非跳转),前端处理
用 expiredSessionStrategy / invalidSessionStrategy 自定义
所以会话超时 = Session 过期时间 + 过期/失效后的处理
会话超时(Session Timeout)——Session 有过期时间(server.servlet.session.timeout=30m),一段时间不活动就过期。失效处理:过期后访问触发认证流程(去登录)、被并发控制踢掉则 expiredUrl 跳转。监听:HttpSessionEventPublisher(发布会话创建/销毁事件,并发控制需要它跟踪会话)。前后端分离返回 JSON(expiredSessionStrategy/invalidSessionStrategy 自定义)。理解「会话超时:Session 过期时间(server.servlet.session.timeout)、过期后访问去登录;HttpSessionEventPublisher 发布会话事件(并发控制需要);前后端分离返回 JSON(expiredSessionStrategy)」,就掌握了会话超时。
五、Session 与 JWT 的会话管理差异
理解 Session 和 JWT 在会话管理上的差异:
Session(有状态)的会话管理:
① 会话在服务端(有状态)
② 能做并发会话控制(服务端知道一个用户几个会话)
③ 能做会话固定攻击防护(登录后换 Session ID)
④ 能主动失效(服务端删 Session)
⑤ 集群要共享 Session(Spring Session + Redis / sticky session)
JWT(无状态)的会话管理:
① 无 Session(STATELESS)——认证在 Token 里
② 并发会话控制失效——服务端不存会话、没法统计
→ 要控制多端登录得自己实现(Redis 存 user→tokens)
③ 会话固定攻击不适用——JWT 不用 Session ID
④ 主动失效难——无状态、没地方删(见登出题)
⑤ 天然易水平扩展(无状态、不用共享 Session)
所以:
Session 场景:Spring Security 的会话管理功能齐全好用
JWT 场景:STATELESS,很多会话管理功能失效,要自己实现
选择会话管理策略:
传统 Web、需要并发控制/主动失效 → Session(会话管理功能全)
REST API、易扩展、无状态 → JWT(但会话管理要自己搞)
Session 和 JWT 的会话管理差异:Session(有状态)——会话在服务端、能做并发会话控制/会话固定防护/主动失效、集群要共享 Session;JWT(无状态)——STATELESS 无 Session、并发会话控制失效(不存会话没法统计,要自己实现)、会话固定攻击不适用(不用 Session ID)、主动失效难、但天然易水平扩展。所以 Session 场景会话管理功能全、JWT 场景很多功能失效要自己实现。理解「Session 有状态:能并发控制/会话固定防护/主动失效、集群要共享;JWT 无状态:并发控制失效(要自己实现)/会话固定不适用/主动失效难、但易扩展;Session 会话管理全、JWT 要自己搞」,就理解了两者的会话管理差异。
六、实践建议
总结会话管理的实践建议:
实践建议(Session 场景):
① 会话固定攻击防护——用默认(changeSessionId),已开启
② 并发会话控制——按需(如金融系统限一个会话)
maximumSessions(1) + 选踢旧/阻止新
③ 合理的会话超时(如 30 分钟)
④ 集群——用 Spring Session + Redis 共享会话
⑤ 前后端分离——会话失效返回 JSON
实践建议(JWT 场景):
① SessionCreationPolicy.STATELESS(不用 Session)
② 并发控制、主动失效要自己实现(Redis 存 user→tokens)
③ 会话固定攻击不适用(不用 Session)
④ 靠 Token 过期 + 黑名单/Refresh 控制
安全要点:
① 会话固定攻击防护(登录后换 Session ID,默认已开)
② 敏感系统限制并发会话(防账号共用/盗用)
③ 合理超时(太长风险大、太短体验差)
④ HTTPS + Cookie HttpOnly/Secure(防 Session ID 被盗)
核心总结:
会话创建策略(STATELESS 用于 JWT)
并发会话控制(限制多端登录,依赖服务端存 Session)
会话固定攻击防护(登录后换 Session ID,默认开)
Session 场景功能全,JWT 场景多数功能失效要自己实现
会话管理实践(Session 场景):会话固定防护用默认、并发控制按需(金融系统限一个)、合理超时、集群用 Spring Session+Redis、前后端分离返回 JSON;(JWT 场景):STATELESS、并发控制/主动失效自己实现(Redis 存 user→tokens)、靠 Token 过期+黑名单。安全要点:会话固定防护(默认开)、敏感系统限并发会话、合理超时、HTTPS + Cookie HttpOnly/Secure。理解「实践:Session 场景(会话固定默认/并发控制按需/超时/集群 Redis)、JWT 场景(STATELESS/并发主动失效自己实现);安全:会话固定防护/限并发/合理超时/HTTPS+HttpOnly」,就掌握了会话管理的实践。
记忆钩子:「Spring Security 会话管理:①会话创建策略 SessionCreationPolicy(ALWAYS/IF_REQUIRED 默认/NEVER/STATELESS-JWT 不用 Session)②并发会话控制 maximumSessions(n)限制账号会话数、超限 maxSessionsPreventsLogin(false 踢旧的顶号/true 阻止新登录)、依赖服务端存 Session(STATELESS/JWT 场景失效要自己实现)③会话固定攻击防护(攻击:攻击者先拿 Session ID 诱导受害者用它登录、登录前后 ID 不变则冒充;防护:登录后换新 Session ID,Spring Security 默认 changeSessionId 已开)④会话超时(过期时间+失效处理);Session 有状态会话管理功能全、JWT 无状态多数功能失效要自己搞」。
七、常见误区与追问
- 误区:会话固定攻击防护要手动配置。 Spring Security 默认已开启(用 changeSessionId 策略)——登录成功后自动更换新的 Session ID,让攻击者预先知道的旧 Session ID 失效;一般不用手动配,除非要改成其他策略(newSession/migrateSession)。
- 误区:JWT 场景也能用 Spring Security 的并发会话控制。 失效——并发会话控制要跟踪「一个用户有几个会话」,依赖服务端存储 Session(SessionRegistry);JWT 是无状态的(STATELESS,服务端不存 Session),没法统计会话数,这个功能用不了;JWT 要控制多端登录得自己实现(如 Redis 存 user→tokens)。
- 误区:maximumSessions(1) 就是不让重复登录。 要看策略——maxSessionsPreventsLogin(false)(默认)是「新登录成功、踢掉最旧的会话」(顶号);maxSessionsPreventsLogin(true) 才是「阻止新登录」(已达上限、新的登不进来);两种效果不同。
- 误区:STATELESS 只是不创建 Session。 STATELESS 是「完全不用 Session」——不创建也不使用,每个请求都要重新认证(如每次带 JWT);适合无状态的 REST API/JWT 场景;这也意味着依赖 Session 的功能(并发会话控制、会话固定防护、服务端主动失效)都用不了。
- 追问:什么是会话固定攻击,怎么防护? 攻击者先获取一个合法的 Session ID,诱导受害者用这个 Session ID 去登录(如给受害者一个带 jsessionid 的链接);如果登录前后 Session ID 不变,受害者登录成功后,攻击者就能用同一个 Session ID 冒充已登录的受害者;防护办法是登录成功后立即更换一个新的 Session ID(Spring Security 默认用 changeSessionId 策略),这样攻击者手里的旧 Session ID 就作废了、冒充不了。
- 追问:并发会话控制怎么实现「一个账号只能一处登录」? 配置 maximumSessions(1) 限制最多一个会话;选择超限策略——maxSessionsPreventsLogin(false) 是新登录踢掉旧会话(顶号,常见)、true 是阻止新登录(旧的还在、新的登不了);实现依赖 SessionRegistry(跟踪用户会话),集群环境要用 Spring Session + Redis 共享会话信息;注意 JWT/STATELESS 场景这功能失效(要自己用 Redis 实现)。
- 追问:Session 和 JWT 在会话管理上有什么区别? Session 是有状态的(会话存服务端):能做并发会话控制(服务端知道一个用户几个会话)、会话固定攻击防护(登录后换 Session ID)、主动失效(删 Session),但集群要共享 Session;JWT 是无状态的(STATELESS):认证在 Token 里、服务端不存会话,所以并发会话控制失效(没法统计会话数)、会话固定攻击不适用(不用 Session ID)、主动失效难(没地方删),但天然易水平扩展;Session 场景会话管理功能齐全,JWT 场景很多功能要自己实现。
八、加强记忆
Spring Security 的会话管理控制「Session 怎么建、能开几个、怎么防攻击」:① 会话创建策略(SessionCreationPolicy)——ALWAYS/IF_REQUIRED(默认,传统 Session 登录)/NEVER/STATELESS(完全不用 Session,JWT/REST API 用);② 并发会话控制——maximumSessions(n) 限制账号会话数(防账号共用),超限策略 maxSessionsPreventsLogin(false) 新登录踢旧的(顶号)/=true 阻止新登录,依赖服务端存 Session(SessionRegistry,集群用 Spring Session+Redis),STATELESS/JWT 场景失效要自己实现;③ 会话固定攻击防护——攻击原理是「攻击者先拿一个 Session ID、诱导受害者用它登录,登录前后 ID 不变则攻击者冒充」,防护是登录后换新 Session ID(Spring Security 默认 changeSessionId 已开启);④ 会话超时——过期时间 + 失效处理。Session(有状态)会话管理功能齐全(能并发控制/会话固定防护/主动失效);JWT(无状态)多数功能失效(并发控制/主动失效要自己实现、会话固定不适用),但易水平扩展。一句话「会话管理:会话创建策略(STATELESS 用于 JWT)、并发会话控制(maximumSessions 限会话数,超限踢旧的/阻止新登录,依赖服务端存 Session,JWT 失效)、会话固定攻击防护(登录后换 Session ID,默认 changeSessionId)、会话超时;Session 有状态功能全、JWT 无状态多数功能要自己搞」。