Spring Security中Remember-me功能失效问题求助
删除JSESSIONID或重启浏览器后,向服务器发起的下一次请求会重置remember-me Cookie,无法正常确认会话。具体表现为:
- 请求授权路径时,
exceptionHandling返回401状态码 - 响应头中出现连续两个
Set-Cookie:Set-Cookie: remember-me=; Max-Age=0; Expires=Thu, 01-Jan-1970 00:00:10 GMT; Path=/ Set-Cookie: remember-me=aFRLMWhLYkhmNzVLMWh6M2I0b2ZYQSUzRCUzRDo2Snd1ZWFkcTFZRGlzZXp4RjAwRHlBJTNEJTNE; Max-Age=1209600; Expires=Thu, 29-Jun-2023 23:40:17 GMT; Path=/; HttpOnly - 数据库中已创建
persistent_logins表,每次请求都会生成新的remember-me token
当前Spring Security配置代码如下:
http .authenticationManager(authManager(http)) .authorizeHttpRequests() .requestMatchers("/auth/sendCode", "/login").permitAll() .requestMatchers("/api/*").hasAuthority("USER") .anyRequest() .authenticated() .and() .exceptionHandling() .authenticationEntryPoint(new HttpStatusEntryPoint(HttpStatus.UNAUTHORIZED)) .and() .formLogin().passwordParameter("code").usernameParameter("email") .successHandler((request, response, authentication) -> { response.setStatus(HttpServletResponse.SC_OK); }).failureHandler((request, response, exception) -> response.setStatus(HttpServletResponse.SC_UNAUTHORIZED)) .permitAll() .and() .logout() .logoutSuccessUrl("/") .deleteCookies("JSESSIONID") .and() .rememberMe() .tokenRepository(persistentTokenRepository()) .userDetailsService(userDetailsService()) .alwaysRemember(true) .tokenValiditySeconds(24*60*60*14) .key("Mykey"); return http.build(); @Bean public PersistentTokenRepository persistentTokenRepository(){ JdbcTokenRepositoryImpl tokenRepository = new JdbcTokenRepositoryImpl(); tokenRepository.setDataSource(dataSource); return tokenRepository; }
1. 自定义认证入口点阻断了remember-me流程
你配置的HttpStatusEntryPoint会直接返回401响应,跳过了Spring Security默认的remember-me认证逻辑。ExceptionTranslationFilter原本会在未认证时触发remember-me的认证尝试,但自定义入口点直接终止了流程,导致系统先清除旧的remember-me Cookie,后续又尝试生成新的,最终出现重复的Set-Cookie头。
解决办法:
替换HttpStatusEntryPoint为支持remember-me的入口逻辑。如果需要保持返回401而非跳转,可以自定义入口点,确保调用remember-me的认证流程:
.exceptionHandling() .authenticationEntryPoint((request, response, authException) -> { // 先触发remember-me认证尝试 RememberMeAuthenticationFilter rememberMeFilter = request.getServletContext().getAttribute(RememberMeAuthenticationFilter.class.getName()); if (rememberMeFilter != null) { rememberMeFilter.doFilter(request, response, chain -> {}); } // 如果认证失败再返回401 if (!SecurityContextHolder.getContext().isAuthenticated()) { response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); } })
或者使用默认的LoginUrlAuthenticationEntryPoint,如果允许跳转登录页的话。
2. alwaysRemember(true)强制频繁刷新token
这个配置会让系统在每次请求时都生成新的remember-me token,哪怕当前会话已经通过remember-me认证。这直接导致了Cookie被频繁重置,违背“有效期内不重置”的需求。
解决办法:
移除alwaysRemember(true)配置,让remember-me仅在用户未通过其他方式认证时生效,且认证成功后在有效期内保持token稳定:
.rememberMe() .tokenRepository(persistentTokenRepository()) .userDetailsService(userDetailsService()) // 移除.alwaysRemember(true) .tokenValiditySeconds(24*60*60*14) .key("Mykey");
3. 认证管理器未注册RememberMeAuthenticationProvider
如果authManager(http)的自定义配置中没有添加RememberMeAuthenticationProvider,remember-me的认证请求会无法通过,系统会认为用户未认证,进而触发重新生成token的逻辑。
解决办法:
确保认证管理器中注册了该Provider,且key与remember-me配置的key一致:
@Bean public AuthenticationManager authManager(HttpSecurity http) throws Exception { AuthenticationManagerBuilder auth = http.getSharedObject(AuthenticationManagerBuilder.class); // 你的其他认证配置(比如DaoAuthenticationProvider) auth.authenticationProvider(new RememberMeAuthenticationProvider("Mykey")); return auth.build(); }
4. Cookie属性或路径不匹配
检查请求路径是否在remember-me Cookie的Path=/覆盖范围内,如果存在子路径的特殊拦截配置,可能导致Cookie无法被正确识别,触发重新生成。另外排查是否有其他过滤器在修改或清除remember-me Cookie。
解决办法:
确认所有需要remember-me认证的请求路径都在Path=/范围内,排查项目中其他拦截器、过滤器对Cookie的处理逻辑,避免冲突。
内容的提问来源于stack exchange,提问作者Ammolite

