Spring Security中SecurityConfiguration与自定义过滤器循环依赖问题
问题根源分析
你遇到的循环依赖本质是:SecurityConfiguration 构造注入了 CredentialsAuthenticationFilter,而 CredentialsAuthenticationFilter 依赖 AuthenticationManager;同时 AuthenticationManager 的创建依赖 AuthenticationConfiguration,后者又会引用你在 SecurityConfiguration 中定义的 AuthenticationProvider,形成闭环。
添加@Lazy无效的原因是:CredentialsAuthenticationFilter被标记为@Component,Spring启动时会优先实例化它,此时AuthenticationManager的Bean还处于创建流程中,懒加载的代理尚未完成初始化,而UsernamePasswordAuthenticationFilter的父类AbstractAuthenticationProcessingFilter在初始化阶段会强制校验authenticationManager非空,直接抛出"authenticationManager must be specified"错误。
解决方案
方案1:手动在SecurityConfiguration中声明Filter Bean(推荐)
移除CredentialsAuthenticationFilter上的@Component注解,改为在配置类中手动创建Bean,完全控制依赖注入顺序,彻底避开循环:
修改CredentialsAuthenticationFilter
public class CredentialsAuthenticationFilter extends UsernamePasswordAuthenticationFilter { private final AuthenticationManager authenticationManager; private final JwtIssuer jwtIssuer; private final TokenService tokenService; private final UserService userService; public CredentialsAuthenticationFilter( AuthenticationManager authenticationManager, JwtIssuer jwtIssuer, TokenService tokenService, UserService userService ) { this.authenticationManager = authenticationManager; this.jwtIssuer = jwtIssuer; this.tokenService = tokenService; this.setRequiresAuthenticationRequestMatcher( new AntPathRequestMatcher("/api/v1/users/login", "POST")); this.userService = userService; // 手动向父类传递AuthenticationManager,确保非空校验通过 super.setAuthenticationManager(authenticationManager); } // 其余方法保持不变 }
修改SecurityConfiguration
@EnableWebSecurity @Configuration @RequiredArgsConstructor public class SecurityConfiguration { private final JwtAuthenticationFilter jwtAuthenticationFilter; private final CustomUserDetailsService userDetailsService; private final LogoutHandler logoutHandler; // 注入创建Filter所需的其他依赖 private final JwtIssuer jwtIssuer; private final TokenService tokenService; private final UserService userService; // 声明CredentialsAuthenticationFilter的Bean @Bean public CredentialsAuthenticationFilter credentialsAuthenticationFilter(AuthenticationManager authenticationManager) { return new CredentialsAuthenticationFilter(authenticationManager, jwtIssuer, tokenService, userService); } @Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { http .cors(Customizer.withDefaults()) .csrf(AbstractHttpConfigurer::disable) .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) .authorizeHttpRequests(registry -> registry .requestMatchers(HttpMethod.POST, "/api/v1/trainees", "/api/v1/trainers").permitAll() .anyRequest().authenticated()) .addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class) // 使用配置类中声明的Filter Bean .addFilterBefore(credentialsAuthenticationFilter(authenticationManager(null)), UsernamePasswordAuthenticationFilter.class) .logout(logout -> logout .logoutUrl("/api/v1/users/logout") .addLogoutHandler(logoutHandler) .logoutSuccessHandler((request, response, authentication) -> { SecurityContextHolder.clearContext(); })); return http.build(); } // 其余方法保持不变 }
方案2:改用Setter注入AuthenticationManager(兼容现有@Component注解)
如果不想移除@Component,将构造器注入改为Setter注入,配合@Lazy延迟依赖获取,同时手动给父类设置AuthenticationManager:
@Component public class CredentialsAuthenticationFilter extends UsernamePasswordAuthenticationFilter { private AuthenticationManager authenticationManager; private final JwtIssuer jwtIssuer; private final TokenService tokenService; private final UserService userService; // 仅注入无循环的依赖 public CredentialsAuthenticationFilter( JwtIssuer jwtIssuer, TokenService tokenService, UserService userService ) { this.jwtIssuer = jwtIssuer; this.tokenService = tokenService; this.setRequiresAuthenticationRequestMatcher( new AntPathRequestMatcher("/api/v1/users/login", "POST")); this.userService = userService; } // Setter注入,@Lazy延迟加载 @Lazy @Autowired public void setAuthenticationManager(AuthenticationManager authenticationManager) { this.authenticationManager = authenticationManager; // 必须调用父类方法,满足父类的非空校验要求 super.setAuthenticationManager(authenticationManager); } // 其余方法保持不变 }
这种方式通过延迟AuthenticationManager的注入时机,避开启动阶段的循环依赖,同时手动完成父类的参数设置,解决报错问题。
内容的提问来源于stack exchange,提问作者j0hnny.b

