Spring Security手动认证兼容过滤器与单会话控制问题求助
你遇到的核心问题是直接设置SecurityContextHolder绕开了Spring Security的完整认证流程,导致配置的CompositeSessionAuthenticationStrategy未被触发,进而单会话控制规则失效。以下是几种更合理的解决办法,避免手动调用单个策略:
方案1:通过AuthenticationManager+AuthenticationSuccessHandler走完整流程
放弃直接设置SecurityContextHolder,借助Spring Security核心组件完成认证后处理,自动触发Session管理逻辑。
代码实现
@Autowired private AuthenticationManager authenticationManager; @Autowired private AuthenticationSuccessHandler authenticationSuccessHandler; // 演示登录/SAML回调统一认证方法 public void authenticate(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { // 1. 构造预认证Token:演示登录用硬编码用户,SAML场景替换为Assertion解析出的用户标识 PreAuthenticatedAuthenticationToken authToken = new PreAuthenticatedAuthenticationToken( "demo-user", null, AuthorityUtils.createAuthorityList("ROLE_USER") ); authToken.setDetails(new WebAuthenticationDetails(request)); // 2. 让AuthenticationManager处理认证(预认证Token通常直接通过) Authentication authenticated = authenticationManager.authenticate(authToken); // 3. 调用SuccessHandler,自动触发SessionAuthenticationStrategy逻辑 authenticationSuccessHandler.onAuthenticationSuccess(request, response, authenticated); }
生效原因
Spring Security默认的AuthenticationSuccessHandler(如SavedRequestAwareAuthenticationSuccessHandler)内部会调用你配置的CompositeSessionAuthenticationStrategy,包含单会话控制、会话固定保护等所有Session相关规则,完全贴合框架原生逻辑。
方案2:直接复用容器中的CompositeSessionAuthenticationStrategy
如果不想改动现有认证逻辑,直接注入Spring自动装配好的CompositeSessionAuthenticationStrategy,在设置SecurityContextHolder后手动触发其方法,确保所有Session策略生效。
代码实现
@Autowired private CompositeSessionAuthenticationStrategy sessionAuthenticationStrategy; @Autowired private SecurityContextRepository securityContextRepository; public void manualAuthenticate(HttpServletRequest request, HttpServletResponse response) { // 构造并设置认证信息到SecurityContext PreAuthenticatedAuthenticationToken authToken = new PreAuthenticatedAuthenticationToken( "demo-user", null, AuthorityUtils.createAuthorityList("ROLE_USER") ); authToken.setDetails(new WebAuthenticationDetails(request)); SecurityContext context = SecurityContextHolder.createEmptyContext(); context.setAuthentication(authToken); SecurityContextHolder.setContext(context); // 先将SecurityContext保存到仓库(如HttpSession) securityContextRepository.saveContext(context, request, response); // 调用Composite策略,触发所有Session管理逻辑 sessionAuthenticationStrategy.onAuthentication(authToken, request, response); }
生效原因
CompositeSessionAuthenticationStrategy是Spring根据你配置的sessionManagement自动组合的策略集合(包含ConcurrentSessionControlAuthenticationStrategy、SessionFixationProtectionStrategy等),调用它能保证所有Session配置规则都生效,比手动调用单个策略更全面。
方案3:自定义PreAuthenticatedFilter整合到过滤器链
如果想让两种登录方式完全融入Spring Security过滤器链,避免手动处理认证逻辑,可以自定义PreAuthenticatedProcessingFilter子类,适配演示登录和SAML回调场景。
步骤1:自定义Filter
public class CustomPreAuthFilter extends AbstractPreAuthenticatedProcessingFilter { public CustomPreAuthFilter(AuthenticationManager authenticationManager) { super(authenticationManager); } @Override protected Object getPreAuthenticatedPrincipal(HttpServletRequest request) { // 根据请求判断场景:演示登录URL或SAML回调参数 if ("/demo-login".equals(request.getRequestURI())) { return "demo-user"; // 硬编码演示用户 } else if (request.getParameter("SAMLResponse") != null) { // 解析SAML Assertion获取用户标识 return parseSamlAssertion(request.getParameter("SAMLResponse")); } return null; } @Override protected Object getPreAuthenticatedCredentials(HttpServletRequest request) { // 预认证场景凭证可设为null return null; } private String parseSamlAssertion(String samlResponse) { // 你的SAML Assertion解析逻辑 return "saml-user-id"; } }
步骤2:配置到SecurityFilterChain
@Bean public CustomPreAuthFilter customPreAuthFilter(AuthenticationManager authenticationManager) { CustomPreAuthFilter filter = new CustomPreAuthFilter(authenticationManager); // 设置Filter处理的URL规则 filter.setFilterProcessesUrl("/auth/**"); // 配置成功/失败处理器 filter.setAuthenticationSuccessHandler(new SavedRequestAwareAuthenticationSuccessHandler()); filter.setAuthenticationFailureHandler(new SimpleUrlAuthenticationFailureHandler("/login-error")); return filter; } @Bean public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { http // 将自定义Filter添加到UsernamePasswordAuthenticationFilter之前 .addFilterBefore(customPreAuthFilter(authenticationManager()), UsernamePasswordAuthenticationFilter.class) .sessionManagement(session -> session .maximumSessions(1) .maxSessionsPreventsLogin(true) ) // 放行演示登录和SAML回调URL .authorizeHttpRequests(auth -> auth .requestMatchers("/demo-login", "/saml-callback").permitAll() .anyRequest().authenticated() ); return http.build(); }
生效原因
自定义Filter完全融入Spring Security过滤器链,认证流程会自动触发所有后续处理,包括SessionManagementFilter调用CompositeSessionAuthenticationStrategy,单会话控制规则自然生效,同时统一了两种登录方式的处理逻辑,更易于长期维护。
内容的提问来源于stack exchange,提问作者Marcin

