
本文介绍如何通过 spring security 的并发会话控制机制,确保每个用户在同一时间只能拥有一个有效登录会话,防止 jwt 登录场景下的多端重复登录问题。
本文介绍如何通过 spring security 的并发会话控制机制,确保每个用户在同一时间只能拥有一个有效登录会话,防止 jwt 登录场景下的多端重复登录问题。
在基于 JWT 的 Spring Boot REST 应用中,由于 JWT 本身是无状态的(stateless),Spring Security 默认的 maximumSessions(1) 配置无法直接生效——因为该机制依赖于服务端的 HttpSession 状态跟踪,而 JWT 认证通常禁用或绕过了传统 session。但若你的应用仍保留了基于 session 的认证流程(例如表单登录 + JWT token 发放混合模式),或你希望在 session 层面强制单会话,则必须确保 Spring Security 的并发会话管理功能完整启用。
关键前提是:Spring Security 的并发会话控制依赖 HttpSession 生命周期事件监听。若未注册 HttpSessionEventPublisher,SessionRegistry 将无法感知 session 的创建与销毁,导致 maximumSessions(1) 形同虚设。
✅ 正确配置步骤如下:
-
注册必需的事件发布器 Bean(缺一不可):
@Bean public HttpSessionEventPublisher httpSessionEventPublisher() { return new HttpSessionEventPublisher(); } -
完善 SecurityConfig 中的会话管理配置(注意:需配合基于 session 的认证方式):
@Override protected void configure(HttpSecurity http) throws Exception { http .authorizeRequests() .antMatchers("/admin/**").hasRole("ADMIN") .antMatchers("/user/**").hasAnyRole("USER", "ADMIN") .anyRequest().authenticated() .and() .formLogin() // 注意:此配置启用基于 session 的表单登录 .loginPage("/login") .permitAll() .and() .logout() .logoutSuccessUrl("/login?logout") .and() .sessionManagement() .sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED) // 允许 session 创建 .invalidSessionUrl("/login?invalid-session") .maximumSessions(1) .maxSessionsPreventsLogin(true) // 新登录时拒绝旧会话续用 .expiredUrl("/login?expired") // 会话过期跳转页 .sessionRegistry(sessionRegistry()) // 推荐显式注入 registry .and() .sessionFixation() .migrateSession(); // 登录后更换 session ID,防 fixation 攻击 } -
(可选但推荐)显式配置 SessionRegistry:
@Bean public SessionRegistry sessionRegistry() { return new SessionRegistryImpl(); }
⚠️ 重要注意事项:
- 若你完全采用 纯 JWT 无状态认证(即 sessionCreationPolicy(SessionCreationPolicy.STATELESS)),上述 session 限制将不适用——此时应改用服务端令牌黑名单(如 Redis 存储已注销/踢出的 JWT jti)、或结合用户登录态唯一标识(如 user_id + device_fingerprint)进行自定义校验。
- maximumSessions(1) 仅对同一 Principal(通常是 username)生效;确保 UserDetailsService 返回的 UserDetails 实现正确 equals() 和 hashCode()。
- maxSessionsPreventsLogin(true) 表示当用户已存在活跃会话时,新登录请求将被拒绝(返回 401 或重定向至 expiredUrl);设为 false 则会强制使旧会话失效(踢下线)。
- 日志中可添加 logging.level.org.springframework.security.core.session=DEBUG 辅助排查会话注册行为。
总结:Spring Security 的单会话控制不是开箱即用的功能,HttpSessionEventPublisher 是触发机制的“开关”。务必确认应用实际使用了 HttpSession,并在配置中补全事件发布器、会话策略与注册中心,才能真正实现“一人一登录”的安全约束。


















