如何复现Spring Security相关的Bean加载顺序异常问题?
背景说明
我们为Spring Boot项目开发了一款内部库,包含两个过滤器:
- SpringMvcFilter:默认加载,通过
@ConditionalOnMissingBean(name = "springSecurityFilterChain")控制,当Spring Security的过滤器链不存在时生效 - SpringSecurityFilter:配置Spring Security时加载,通过
AbstractHttpConfigurer将其添加到Security过滤链中
SpringMvcFilter的自动配置代码:
@AutoConfiguration public class SpringMvcConfig { @Bean @ConditionalOnMissingBean(name = {"springSecurityFilterChain"}) public Filter springMvcFilter() { // implementation } }
SpringSecurityFilter的配置代码:
public class SpringSecurityConfigurer extends AbstractHttpConfigurer<SpringSecurityConfigurer, HttpSecurity> { @Override public void configure(HttpSecurity builder) { builder.addFilterBefore(new SpringSecurityFilter(), BasicAuthenticationFilter.class); } }
问题现象
正常逻辑下,当客户配置Spring Security时,springSecurityFilterChain Bean存在,SpringMvcFilter应该被@ConditionalOnMissingBean阻止加载。但有一位客户反馈两个过滤器均被加载并生效,本地环境无法复现该问题。
本地复现测试(基于Spring Boot 2.7.16)
为排查问题,搭建了测试项目并引入一个包含三个条件Bean的依赖库:
测试依赖库的自动配置类
@AutoConfiguration public class CoolReuseBeans { @Bean("coolReuseIntBean") @ConditionalOnMissingBean(name = {"springSecurityFilterChain"}) public Integer beanInt() { return 5; } @Bean("coolReuseStringBean") @ConditionalOnBean(name = {"springSecurityFilterChain"}) public String beanString() { return "5"; } @Bean("coolReuseBooleanBean") public Boolean beanBoolean() { return true; } }
项目中的Spring Security配置
@Configuration @EnableWebSecurity public class WebSecurityConfiguration { @Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { http .authorizeHttpRequests(requests -> requests .requestMatchers(new AntPathRequestMatcher("/")).permitAll() .anyRequest().authenticated()); return http.build(); } }
启动类(用于打印Bean加载情况)
@SpringBootApplication public class Application { private static ConfigurableApplicationContext applicationContext; public static void main(String[] args) { applicationContext = SpringApplication.run(Application.class, args); hasBean("springSecurityFilterChain"); hasBean("coolReuseIntBean"); hasBean("coolReuseStringBean"); hasBean("coolReuseBooleanBean"); } private static void hasBean(String name) { String[] allBeanNames = applicationContext.getBeanDefinitionNames(); System.out.println(String.format("Did find %s? %s", name, Arrays.stream(allBeanNames).anyMatch(x -> x.equals(name)))); } }
测试结果1:开启@EnableWebSecurity
日志输出:
253: DEBUG 36231 --- [ main] o.s.b.f.s.DefaultListableBeanFactory : Creating shared instance of singleton bean 'springSecurityFilterChain' 366: DEBUG 36231 --- [ main] o.s.b.f.s.DefaultListableBeanFactory : Creating shared instance of singleton bean 'com.example.beans.CoolReuseBeans' 367: DEBUG 36231 --- [ main] o.s.b.f.s.DefaultListableBeanFactory : Creating shared instance of singleton bean 'coolReuseStringBean' 368: DEBUG 36231 --- [ main] o.s.b.f.s.DefaultListableBeanFactory : Creating shared instance of singleton bean 'coolReuseBooleanBean' CoolReuseBeans#beanString matched: - @ConditionalOnBean (names: springSecurityFilterChain; SearchStrategy: all) found bean 'springSecurityFilterChain' (OnBeanCondition) CoolReuseBeans#beanInt: Did not match: - @ConditionalOnMissingBean (names: springSecurityFilterChain; SearchStrategy: all) found beans named springSecurityFilterChain (OnBeanCondition) Did find springSecurityFilterChain? true Did find coolReuseIntBean? false Did find coolReuseStringBean? true Did find coolReuseBooleanBean? true
结果符合预期:springSecurityFilterChain先加载,coolReuseIntBean因条件不满足未被创建。
测试结果2:注释@EnableWebSecurity
日志输出:
366: DEBUG 37070 --- [ main] o.s.b.f.s.DefaultListableBeanFactory : Creating shared instance of singleton bean 'com.example.beans.CoolReuseBeans' 367: DEBUG 37070 --- [ main] o.s.b.f.s.DefaultListableBeanFactory : Creating shared instance of singleton bean 'coolReuseIntBean' 368: DEBUG 37070 --- [ main] o.s.b.f.s.DefaultListableBeanFactory : Creating shared instance of singleton bean 'coolReuseBooleanBean' 385: DEBUG 37070 --- [ main] o.s.b.f.s.DefaultListableBeanFactory : Creating shared instance of singleton bean 'springSecurityFilterChain' CoolReuseBeans#beanInt matched: - @ConditionalOnMissingBean (names: springSecurityFilterChain; SearchStrategy: all) did not find any beans (OnBeanCondition) CoolReuseBeans#beanString: Did not match: - @ConditionalOnBean (names: springSecurityFilterChain; SearchStrategy: all) did not find any beans named springSecurityFilterChain (OnBeanCondition) Did find springSecurityFilterChain? true Did find coolReuseIntBean? true Did find coolReuseStringBean? false Did find coolReuseBooleanBean? true
这里出现异常现象:即使未配置Spring Security,springSecurityFilterChain仍会被加载,只是加载顺序晚于CoolReuseBeans的Bean。
疑惑与求助
唯一能解释客户问题的场景是:springSecurityFilterChain在包含SpringMvcFilter的@AutoConfiguration之后加载,导致@ConditionalOnMissingBean条件满足,SpringMvcFilter被创建;之后springSecurityFilterChain才被加载,最终两个过滤器共存。但本地无法复现这个加载顺序,而且按认知@AutoConfiguration应该在Bean加载的最后阶段处理,不该出现这种顺序问题。
求问有没有人知道该问题的原因,或者能提供复现方法?
内容的提问来源于stack exchange,提问作者Dennis H

