Spring Security会话管理与CSRF详解
定位:第 04 篇,讲透会话形态与认证状态管理、会话固定攻击防护、并发会话控制与 CSRF 防护机制
适用版本:Spring Security 6.x(Spring Boot 3.x)
目录
一、会话与认证状态
1.1 两种会话形态
| 形态 | 机制 | 典型场景 |
|---|---|---|
| 有状态 | 登录后 SecurityContext 存入 HttpSession,后续请求靠会话恢复 | 表单登录的 Web 应用 |
| 无状态 | 不用会话,每次请求携带凭证(JWT),服务端现验 | 前后端分离、微服务 API |
java
// 无状态会话策略(JWT API 标配)
http.sessionManagement(s -> s.sessionCreationPolicy(SessionCreationPolicy.STATELESS));
1.2 会话策略枚举
| 策略 | 行为 |
|---|---|
| ALWAYS | 总是创建会话 |
| IF_REQUIRED | 需要时创建(默认) |
| NEVER | 不主动创建,存在则用 |
| STATELESS | 完全不使用会话 |
1.3 并发会话控制
java
http.sessionManagement(s -> s.maximumSessions(1) // 每账号最多 1 个会话
.maxSessionsPreventsLogin(false)); // 新登录踢掉旧会话
依赖 SessionRegistry 跟踪所有会话;集群环境需共享会话(Redis,Session 主题)才能全局生效。
二、会话固定攻击
2.1 攻击原理
① 攻击者先访问站点拿到一个 sessionId
② 诱导受害者用该 sessionId 登录(如 URL 带 session 参数)
③ 受害者登录后,攻击者用同一 sessionId 直接获得其会话
2.2 防护:登录换 ID
默认策略 migrateSession:
登录成功 → 作废旧 sessionId、生成新的、迁移会话数据
→ 攻击者手里的旧 ID 失效
其他策略:changeSessionId(容器层换 ID)、newSession(全新会话,更彻底)。默认配置已防护,不要随意关闭。
三、CSRF
3.1 攻击原理
前提:用户已登录站点 A(浏览器持有 A 的 Cookie)
① 用户访问恶意站点 B
② B 页面发起对 A 的请求(表单自动提交)
③ 浏览器自动携带 A 的 Cookie → A 以为是用户本人操作
利用的是"浏览器自动携带 Cookie "与"服务端只凭 Cookie 认身份"。
3.2 防护:CSRF Token
① 服务端生成随机 Token,绑定会话,渲染进表单(隐藏域)/下发给前端
② 状态变更请求(POST/PUT/DELETE)必须携带 Token(表单域或 X-CSRF-TOKEN 头)
③ CsrfFilter 校验:无 Token 或不匹配 → 403
④ 只读请求(GET/HEAD/OPTIONS)豁免
恶意站点无法跨站读到 Token(同源策略),所以伪造请求带不上合法 Token。
3.3 豁免场景
无状态 API(JWT/OAuth2 Bearer 认证):身份不靠 Cookie,CSRF 不成立 → 通常禁用
http.csrf(csrf -> csrf.disable()) // 仅限无状态
混合:只对状态变更的会话路径开启,/api/** 豁免
纪律:有状态(Cookie 会话)应用不要关 CSRF;无状态才关,且确保认证确实不依赖 Cookie。
3.4 纵深防护:SameSite
Cookie 的 SameSite=Lax/Strict 让浏览器在跨站请求时不携带 Cookie------现代浏览器默认 Lax,已大幅缓解 CSRF。但它是浏览器行为、有兼容差异,不能替代 Token,只能叠加。
四、安全响应头
Security 默认为响应添加防护头:
| 头 | 作用 |
|---|---|
X-Content-Type-Options: nosniff |
防 MIME 类型嗅探 |
X-Frame-Options: DENY |
防点击劫持(禁止被 iframe 嵌套) |
Cache-Control |
敏感响应禁缓存 |
Content-Security-Policy |
内容安全策略(需显式配置) |
X-XSS-Protection |
旧浏览器 XSS 过滤(已不推荐依赖) |
默认开启是"安全默认值"的又一例;按需覆盖而非关闭。
五、总结
- 会话形态:有状态(会话存上下文)与无状态(STATELESS,JWT);按认证方式选择,混合场景分链配置。
- 会话固定:登录后更换 sessionId(默认 migrateSession)使被盗用的旧 ID 失效;不要关闭。
- 并发控制:maximumSessions + SessionRegistry;集群要共享会话才全局生效。
- CSRF:利用浏览器自动带 Cookie 冒名提交;防护是绑定会话的 Token,状态变更请求必带;无状态 API 才豁免;SameSite 是纵深补充不替代。
- 安全头:默认添加防嗅探/点击劫持等,按需配置 CSP。
六、常见高频面试题
1. 什么是会话固定攻击?Spring Security 怎么防?
要点:攻击者先拿到一个 sessionId 并诱导受害者用它登录,受害者登录后攻击者凭同一 sessionId 直接接管其会话。防护核心是登录成功后更换 sessionId------Security 默认 migrateSession 策略:登录时生成新 sessionId、迁移数据、旧 ID 作废,攻击者持有的旧 ID 失效。其他策略有 changeSessionId/newSession。该防护默认开启,不应关闭。
2. CSRF 攻击的原理是什么?
要点:利用"浏览器自动携带 Cookie"------用户已登录站点 A,访问恶意站点 B 时,B 诱导浏览器向 A 发起状态变更请求(如自动提交表单),浏览器自动带上 A 的 Cookie,A 无法区分这是伪造请求还是用户本人操作。成立条件:身份靠 Cookie、请求未验证来源。危害集中在写操作(转账、改设置),读操作一般无实质影响。
3. CSRF Token 是怎么工作的?
要点:服务端生成与用户会话绑定的随机 Token,下发给页面(表单隐藏域或元标签);所有状态变更请求(POST/PUT/DELETE)必须回传该 Token(表单域或 X-CSRF-TOKEN 头);CsrfFilter 校验 Token 与会话绑定关系,缺失或不匹配则 403。恶意站点因同源策略无法读到受害者页面上的 Token,伪造请求带不上合法 Token 而被拦截。GET 等只读方法豁免。
4. 前后端分离 + JWT 的项目,为什么要关闭 CSRF?
要点:CSRF 成立的前提是身份靠浏览器自动携带的 Cookie。JWT 方案里身份凭证是请求头中的 Token(不自动携带,攻击者拿不到也没法让浏览器自动带),CSRF 攻击不成立,所以通常禁用(配合 SessionCreationPolicy.STATELESS)。但注意:若同时仍用 Cookie 会话(如 OAuth2 登录后的会话),则不能关。是否关取决于认证机制是否依赖 Cookie,而非"是不是前后端分离"。
5. Spring Security 的会话创建策略有哪些?
要点:SessionCreationPolicy 四种------ALWAYS(总是创建)、IF_REQUIRED(需要时创建,默认)、NEVER(不主动创建,有则用)、STATELESS(完全不用会话,JWT API 标配)。选择与认证形态匹配:表单会话登录用默认;无状态 Token 认证用 STATELESS(同时常关 CSRF)。策略影响 SecurityContextRepository 是否持久化上下文。
6. 如何限制同一账号的并发登录?
要点:sessionManagement 配置 maximumSessions(1),配合 maxSessionsPreventsLogin 决定新登录是挤掉旧会话还是拒绝新登录。实现依赖 SessionRegistry 记录所有活跃会话与用户映射。注意:SessionRegistry 默认在单实例内存中------集群部署必须共享会话(如 Spring Session + Redis)才能真正全局限制并发会话。
7. SameSite Cookie 和 CSRF Token 什么关系?
要点:SameSite(Lax/Strict)让浏览器在跨站请求时不携带 Cookie,从源头削弱 CSRF(现代浏览器默认 Lax)。但它是浏览器行为:旧浏览器不支持、兼容模式可能失效、严格模式可能影响正常跨站跳转体验。所以 SameSite 是纵深防御层,不能替代服务端 CSRF Token------有状态应用两者叠加:Token 保证正确性,SameSite 减小攻击面。
8. SecurityContext 在会话模式下如何跨请求保持?
要点:登录成功后,SecurityContext(含 Authentication)由 SecurityContextRepository 保存到 HttpSession;后续请求进入时,安全上下文过滤器(6.x 的 SecurityContextHolderFilter 经仓库读取)从会话恢复上下文到 SecurityContextHolder,供授权使用;请求结束清理。这就是"登录一次、全程有效"的机制。无状态模式不保存,每次请求从 Token 重建。
9. Spring Security 默认设置了哪些安全响应头?
要点:常见的有 X-Content-Type-Options: nosniff(防 MIME 嗅探)、X-Frame-Options: DENY(防点击劫持)、Cache-Control(敏感响应禁缓存)、X-XSS-Protection(旧浏览器)。还可配置 Content-Security-Policy、HSTS(HTTPS 强制)等。默认开启体现安全默认值;按业务需要覆盖个别头(如允许特定站点 iframe),但应说明理由而非整体关闭。
10. 会话管理在集群环境下要注意什么?
要点:默认 HttpSession 存在单实例内存中,集群多实例时:① 会话不共享------请求打到别的实例就丢登录态(需粘性会话或会话共享);② 并发会话限制(SessionRegistry)、会话事件(登录/登出)只在单实例可见。解法:用 Spring Session + Redis 把会话外置共享(Session 主题深讲),实例无状态化,支持水平扩展与优雅停机不丢登录态。
