Spring Security会话超时后自动跳转登录页问题求助
会话超时自动跳转/提示的解决方案
核心问题分析
你当前的配置误区在于:LogoutSuccessHandler仅处理主动登出流程(比如用户点击登出按钮),会话超时属于被动失效场景,不会触发该处理器。这就是调试无法进入LogoutSuccessHandlerService的原因。要实现会话超时后的跳转或提示,需要结合后端会话失效配置与前端请求拦截/定时器检测。
完整可运行示例
1. 后端Spring Security配置修改
SecurityConfig.java
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.builders.WebSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder; @EnableWebSecurity public class SecurityConfig extends WebSecurityConfigurerAdapter { private final AppProperties appProperties; private final LogoutSuccessHandlerService logoutSuccessHandlerService; public SecurityConfig(AppProperties appProperties, LogoutSuccessHandlerService logoutSuccessHandlerService) { this.appProperties = appProperties; this.logoutSuccessHandlerService = logoutSuccessHandlerService; } @Override public void configure(WebSecurity web) { web.ignoring() .antMatchers("/static/**", "/webjars/**", "/css/**", "/fonts/**", "/img/**", "/js/**", "/scripts/**"); } @Override protected void configure(HttpSecurity http) throws Exception { http .formLogin() .loginPage("/login") .permitAll() .defaultSuccessUrl("/", true) .failureUrl("/login?error=true") .loginProcessingUrl("/j_spring_security_check") .and() .authorizeRequests() .anyRequest().authenticated() .and() // 会话超时配置:会话失效后重定向到带过期标记的登录页 .sessionManagement() .invalidSessionUrl("/login?sessionExpired=true") .and() .logout() .invalidateHttpSession(true) .logoutSuccessHandler(logoutSuccessHandlerService) .permitAll() .and() .csrf().disable(); } @Override public void configure(AuthenticationManagerBuilder auth) throws Exception { AppProperties.Security security = appProperties.getSecurity(); auth.inMemoryAuthentication() .withUser(security.getUser()) .password(passwordEncoder().encode(security.getPassword())) .roles(security.getRole()); } @Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } }
LogoutSuccessHandlerService.java
import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.security.core.Authentication; import org.springframework.security.web.authentication.logout.SimpleUrlLogoutSuccessHandler; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; public class LogoutSuccessHandlerService extends SimpleUrlLogoutSuccessHandler { private static final Logger log = LoggerFactory.getLogger(LogoutSuccessHandlerService.class); @Override public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException { log.info("用户主动登出成功"); setDefaultTargetUrl("/login"); super.onLogoutSuccess(request, response, authentication); } }
application-local.yml
server: port: 8086 servlet: session: timeout: 2m tracking-modes: COOKIE
2. 前端处理方案
登录页提示会话过期(login.html)
在登录页添加会话过期提示逻辑:
<!DOCTYPE html> <html> <head> <title>登录</title> </head> <body> <div id="login-form"> <!-- 登录表单内容 --> <input type="text" name="username" placeholder="用户名"> <input type="password" name="password" placeholder="密码"> <button type="submit">登录</button> </div> <!-- 会话过期提示 --> <div id="session-expired-alert" style="display:none; color:red; margin-top:10px;"> 会话已过期,请重新登录 </div> <script src="/js/jquery.min.js"></script> <script> $(function() { // 检查URL参数是否存在sessionExpired const urlParams = new URLSearchParams(window.location.search); if (urlParams.has('sessionExpired')) { $('#session-expired-alert').show(); } }); </script> </body> </html>
全局AJAX请求拦截(处理DataTable等异步请求)
创建全局JS文件(/js/session-handler.js),拦截所有AJAX请求的401未授权响应:
$(function() { // 全局AJAX拦截,处理会话失效的401响应 $.ajaxSetup({ complete: function(xhr) { if (xhr.status === 401) { alert('会话已过期,请重新登录'); window.location.href = '/login?sessionExpired=true'; } } }); // 页面闲置定时器:提前提示用户会话即将过期 let sessionTimeoutTimer; const sessionTimeoutMinutes = 2; // 与后端配置一致 const warningSeconds = (sessionTimeoutMinutes * 60) - 30; // 提前30秒提示 // 重置定时器(用户操作时触发) function resetSessionTimer() { clearTimeout(sessionTimeoutTimer); sessionTimeoutTimer = setTimeout(function() { if (confirm(`会话将在30秒后过期,是否继续保持登录?`)) { // 发送心跳请求维持会话 $.get('/heartbeat'); resetSessionTimer(); } else { // 用户选择登出 window.location.href = '/logout'; } }, warningSeconds * 1000); } // 监听用户操作(点击、输入等) $(document).on('click keydown', resetSessionTimer); // 页面加载时启动定时器 resetSessionTimer(); });
后端心跳接口(可选)
在Controller中添加心跳接口,用于前端维持会话:
import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; @RestController public class HeartbeatController { @GetMapping("/heartbeat") public String heartbeat() { return "ok"; } }
关键说明
- 后端
invalidSessionUrl:会话超时后,用户发起的同步请求会直接重定向到该URL;异步请求则返回401状态码。 - 前端AJAX拦截:覆盖DataTable等异步请求场景,确保所有请求的会话失效都能被捕获并处理。
- 前端定时器:实现主动式会话过期提示,避免用户操作到一半时突然跳转,提升体验。
内容的提问来源于stack exchange,提问作者Арсений Плаксин
相关产品推荐
相关产品推荐

