Spring Boot 3.2.1下如何同时使用Spring Security自定义认证提供者与默认认证提供者
我来帮你解决这个问题!你的核心需求是先通过请求头(X-CLIENT-ID、X-API-KEY)完成认证,失败后自动回退到Basic认证,但目前默认的Basic认证提供者没有被加入到认证管理器中,导致回退逻辑无法生效。
问题原因分析
当你手动调用authenticationManagerBuilder.authenticationProvider()添加自定义认证提供者时,Spring Security会认为你想要完全控制认证提供者的列表,因此不会自动注入默认的提供者(比如用于Basic认证的DaoAuthenticationProvider)。这就是你在debug中只看到自定义RequestHeaderAuthenticationProvider和匿名提供者的原因。
解决方案:显式包含默认认证提供者
要同时使用自定义和默认提供者,你需要在配置中显式把默认的DaoAuthenticationProvider(对应Basic认证)加入到认证管理器中,最便捷的方式是通过关联UserDetailsService来触发默认提供者的自动创建,同时还要注意密码编码器的配置(Spring Security 3.x强制要求密码加密)。
步骤1:添加密码编码器Bean
@Bean PasswordEncoder passwordEncoder() { // 测试环境可以用NoOp,生产环境请替换为BCryptPasswordEncoder等安全编码器 return NoOpPasswordEncoder.getInstance(); }
步骤2:修改SecurityConfiguration配置
调整认证管理器的构建逻辑,同时添加自定义提供者和默认的UserDetailsService关联:
@Configuration public class SecurityConfiguration { private static final String CLIENT_ID_HEADER_NAME = "X-CLIENT-ID"; private static final String API_KEY_HEADER_NAME = "X-API-KEY"; private final RequestHeaderAuthenticationProvider authenticationProvider; public SecurityConfiguration(RequestHeaderAuthenticationProvider authenticationProvider) { this.authenticationProvider = authenticationProvider; } @Bean SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { AuthenticationManagerBuilder authenticationManagerBuilder = http.getSharedObject(AuthenticationManagerBuilder.class); // 1. 添加自定义认证提供者 authenticationManagerBuilder.authenticationProvider(this.authenticationProvider); // 2. 关联UserDetailsService,触发默认DaoAuthenticationProvider的创建(用于Basic认证) authenticationManagerBuilder.userDetailsService(inMemoryUserDetailsManager(passwordEncoder())) .passwordEncoder(passwordEncoder()); http.csrf(CsrfConfigurer::disable) .cors(CorsConfigurer::disable) .formLogin(FormLoginConfigurer::disable) .httpBasic(Customizer.withDefaults()); http.authorizeHttpRequests(request -> request.anyRequest().authenticated()); // 关键:把自定义过滤器放在BasicAuthenticationFilter之前,保证优先执行请求头认证 http.addFilterBefore(requestHeaderAuthenticationFilter(http), BasicAuthenticationFilter.class); return http.build(); } @Bean RequestHeaderAuthenticationFilter requestHeaderAuthenticationFilter(HttpSecurity http) throws Exception { RequestHeaderAuthenticationFilter filter = new RequestHeaderAuthenticationFilter(); filter.setPrincipalRequestHeader(CLIENT_ID_HEADER_NAME); filter.setCredentialsRequestHeader(API_KEY_HEADER_NAME); filter.setExceptionIfHeaderMissing(false); filter.setAuthenticationManager(authenticationManager(http)); return filter; } @Bean AuthenticationManager authenticationManager(HttpSecurity http) throws Exception { AuthenticationManagerBuilder authenticationManagerBuilder = http.getSharedObject(AuthenticationManagerBuilder.class); return authenticationManagerBuilder.build(); } @Bean InMemoryUserDetailsManager inMemoryUserDetailsManager(PasswordEncoder passwordEncoder) { UserDetails admin = User.withUsername("admin") .password(passwordEncoder.encode("admin")) .roles("USER") .build(); return new InMemoryUserDetailsManager(admin); } }
步骤3:验证逻辑
修改完成后,ProviderManager的提供者列表会包含:
RequestHeaderAuthenticationProvider(优先执行)DaoAuthenticationProvider(Basic认证用)AnonymousAuthenticationProvider
此时请求会先尝试请求头认证,失败后自动触发Basic认证,完全符合你的需求。
对你疑问的解答
Spring Security是否在注册自定义Provider为Bean时自动排除默认Provider?
是的。当你手动通过AuthenticationManagerBuilder添加认证提供者时,Spring Security会关闭默认提供者的自动注入逻辑,因为它认为你想要完全掌控认证流程。因此必须显式添加所有需要的提供者,包括默认的那些。
备注:内容来源于stack exchange,提问作者koreanDev

