You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Concurrent Session与Remember-me问题:非记住登录登出勿删持久化令牌

问题分析与解决方案

这个问题的核心在于默认的PersistentTokenBasedRememberMeServices在登出时会删除该用户所有的持久化令牌,不管当前登出的会话是否使用了「记住我」功能。当你在地点2以普通方式(未勾选Remember-me)登录并登出时,触发了logout方法里的tokenRepository.removeUserTokens(username)逻辑,直接清空了该用户的所有令牌,包括地点1生成的有效令牌。

要解决这个问题,我们需要自定义PersistentTokenBasedRememberMeServices的子类,重写logout方法,让它只删除当前会话对应的「记住我」令牌(如果存在的话),而非全部删除。

具体实现步骤

1. 自定义RememberMeServices子类

创建一个继承自PersistentTokenBasedRememberMeServices的类,修改登出时的令牌删除逻辑:

public class CustomPersistentTokenBasedRememberMeServices extends PersistentTokenBasedRememberMeServices {

    private final PersistentTokenRepository tokenRepository;

    public CustomPersistentTokenBasedRememberMeServices(String key, UserDetailsService userDetailsService, PersistentTokenRepository tokenRepository) {
        super(key, userDetailsService, tokenRepository);
        this.tokenRepository = tokenRepository;
    }

    @Override
    public void logout(HttpServletRequest request, HttpServletResponse response, Authentication authentication) {
        // 仅当当前认证是通过「记住我」方式触发时,才删除对应的令牌
        if (authentication instanceof RememberMeAuthenticationToken) {
            String rememberMeCookie = extractRememberMeCookie(request);
            if (rememberMeCookie != null) {
                String[] cookieTokens = decodeCookie(rememberMeCookie);
                // 解析出令牌的系列号(series),只删除该系列号对应的令牌
                if (cookieTokens.length == 2) {
                    String series = cookieTokens[0];
                    tokenRepository.removeToken(series);
                }
            }
        }
        // 调用父类逻辑清除cookie等其他登出操作
        super.logout(request, response, authentication);
    }
}

2. 配置Spring Security使用自定义类

在你的Spring Security配置类中,替换默认的RememberMeServices为我们自定义的实现:

@Configuration
@EnableWebSecurity
public class SecurityConfig {

    private final UserDetailsService userDetailsService;
    private final PersistentTokenRepository persistentTokenRepository;

    // 通过构造注入依赖(推荐)
    public SecurityConfig(UserDetailsService userDetailsService, PersistentTokenRepository persistentTokenRepository) {
        this.userDetailsService = userDetailsService;
        this.persistentTokenRepository = persistentTokenRepository;
    }

    @Bean
    public RememberMeServices customRememberMeServices() {
        CustomPersistentTokenBasedRememberMeServices rememberMeServices = new CustomPersistentTokenBasedRememberMeServices(
                "your-remember-me-secret-key", // 建议从配置文件读取,比如@Value("${remember-me.key}")
                userDetailsService,
                persistentTokenRepository
        );
        // 根据需求配置cookie属性
        rememberMeServices.setCookieName("remember-me");
        rememberMeServices.setTokenValiditySeconds(7 * 24 * 3600); // 7天有效期
        return rememberMeServices;
    }

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http
                .authorizeHttpRequests(auth -> auth.anyRequest().authenticated())
                .formLogin(form -> form.permitAll())
                .logout(logout -> logout
                        .permitAll()
                        .rememberMeServices(customRememberMeServices())
                )
                .rememberMe(rememberMe -> rememberMe
                        .rememberMeServices(customRememberMeServices())
                );
        return http.build();
    }
}

方案效果说明

  • 当用户在地点1勾选「记住我」登录后,数据库会生成对应的持久化令牌;
  • 用户在地点2以普通方式(未勾选「记住我」)登录并登出时,由于当前认证不是RememberMeAuthenticationToken,不会触发令牌删除逻辑,地点1的令牌会保留;
  • 如果用户在地点2也勾选「记住我」登录,会生成新的独立令牌,登出时只会删除该会话对应的令牌,不影响地点1的令牌;
  • 当用户在地点1主动登出时,会删除地点1对应的令牌,不影响其他地点的令牌(如果有的话)。

内容的提问来源于stack exchange,提问作者JustFig

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.27 09:31:51