1. 项目概述:为什么我们需要关注会话安全与共享?
在构建现代Web应用时,会话(Session)是维持用户状态的核心机制。无论是电商网站的购物车,还是后台管理系统的登录状态,都离不开它。但就像一把钥匙能打开一扇门,如果这把钥匙被复制或窃取,安全防线就会瞬间崩塌。Spring Security 6.x 作为Java生态中首屈一指的安全框架,它在会话管理上提供了强大的“锁”和“钥匙管理机制”。今天,我们不谈那些基础的配置,而是深入到两个实战中极易出问题,却又至关重要的领域: 会话固定攻击防护 和 Session共享 。
很多开发者对会话的理解还停留在 HttpSession.setAttribute() 和 getAttribute() 的层面,认为用了Spring Security的默认配置就高枕无忧。直到线上出现用户A登录后,看到了用户B的数据这种“灵异事件”,或者安全扫描报告里亮起了“会话固定漏洞”的红灯,才开始手忙脚乱。实际上,Spring Security已经为我们内置了成熟的防护策略,但你需要理解它、配置它,甚至在某些分布式场景下扩展它。这不仅仅是配置几个参数,更是对Web安全底层逻辑的一次梳理。接下来,我会结合自己的踩坑经验,带你拆解这两个主题,让你不仅能配,更能明白为什么要这么配。
2. 会话固定攻击防护:原理、策略与实战配置
会话固定攻击(Session Fixation Attack)是一种相对古老但依然有效的攻击手段。它的原理很简单:攻击者先获取一个有效的会话ID(比如通过访问网站获得一个JSESSIONID),然后想方设法让受害者使用这个 已知的会话ID 进行登录。一旦受害者用这个ID成功认证,攻击者手中的同一个会话ID也就拥有了受害者的全部权限,从而实现对受害者账户的劫持。
2.1 攻击场景还原与Spring Security的防御哲学
想象一个不安全的登录流程:
- 用户(受害者)访问网站首页,服务器分配一个会话ID:
JSESSIONID=abc123。此时会话是未认证状态。 - 攻击者通过某种方式(比如构造一个包含
JSESSIONID=abc123的链接发给受害者)让受害者使用这个会话。 - 受害者点击链接,在
abc123这个会话上下文中输入用户名密码并登录。 - 登录成功后,服务器将
abc123这个会话标记为“已认证”,关联上受害者的身份。 - 此时,攻击者因为同样持有
JSESSIONID=abc123,他无需密码就直接以受害者身份进入了系统。
Spring Security的防御核心在于: 决不允许认证前后的会话是同一个 。它通过在认证成功后,强制变更会话标识符来粉碎这种攻击。在Spring Security 6.x中,这主要通过 SessionAuthenticationStrategy 接口及其实现类来完成。
2.2 核心防御策略 SessionFixationProtectionStrategy
Spring Security默认启用的就是会话固定攻击防护。其核心实现类是 SessionFixationProtectionStrategy 。它的工作流程如下:
- 请求认证 :用户提交登录请求。
- 认证成功 :
AuthenticationSuccessHandler被触发。 - 策略执行 :
SessionFixationProtectionStrategy介入,它会做一件关键事:调用HttpServletRequest.changeSessionId()(对于Servlet 3.1+容器)或模拟其行为。这个方法会创建一个全新的会话,并将旧会话中的所有属性复制到新会话中,然后使旧的会话ID失效。 - 结果 :用户登录后,浏览器中的
JSESSIONID已经变成了一个全新的、攻击者不知道的值。攻击者手中旧的JSESSIONID随之作废。
在配置上,Spring Security 6.x 将其整合在了 SessionManagementConfigurer 中。我们来看一个典型的配置片段:
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.session.SessionFixationProtectionStrategy;
@Configuration
public class SecurityConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests(authz -> authz
.requestMatchers("/public/**").permitAll()
.anyRequest().authenticated()
)
.formLogin(form -> form
.loginPage("/login")
.permitAll()
)
.sessionManagement(session -> session
.sessionFixation(fixation -> fixation
.changeSessionId() // 这是默认且推荐的方式,对应 SessionFixationProtectionStrategy
// .migrateSession() // Servlet 3.0 或旧容器的备选,行为类似
// .newSession() // 创建全新会话,不复制属性,慎用!
// .none() // 禁用防护,极度危险!
)
// 其他会话配置...
);
return http.build();
}
}
注意
399



被折叠的 条评论
为什么被折叠?



