升级Spring Security 5.7.3遇UserDetailsService Bean缺失问题求助
异常信息
Caused by: org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'org.springframework.security.core.userdetails.UserDetailsService' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {@org.springframework.beans.factory.annotation.Autowired(required=true)} at app//org.springframework.beans.factory.support.DefaultListableBeanFactory.raiseNoMatchingBeanFound(DefaultListableBeanFactory.java:1799) ...
相关代码
WebSecurityConfigAD配置类
@Slf4j @Configuration @EnableWebSecurity @EnableGlobalMethodSecurity(prePostEnabled = true) @ComponentScan public class WebSecurityConfigAD implements WebMvcConfigurer { @Autowired private ApplicationContext applicationContext; @Autowired private UserRepository repo; @Value( "${ad.domain}" ) private String AD_DOMAIN; @Value( "${ad.url}" ) private String AD_URL; @Value( "${ad.searchfilter}") private String SEARCH_FILTER; @Autowired private ConfigPDF configPdf; @Autowired(required=true) private MyUserDetailService userDetailsService; @Bean public SecurityFilterChain filterChain( HttpSecurity http ) throws Exception { AuthenticationManagerBuilder authManagerBuilder = http.getSharedObject(AuthenticationManagerBuilder.class); http.authorizeRequests().antMatchers( "/resources/**" ).permitAll().antMatchers( "/css/**" ).permitAll().antMatchers( "/img/**" ).permitAll().antMatchers( "/images/**" ).permitAll().anyRequest().authenticated().and().formLogin().loginPage( "/login.html" ).loginProcessingUrl( "/login" ) .permitAll().successHandler( formLoginSuccessHandler() ); http.logout().logoutRequestMatcher( new AntPathRequestMatcher( "/logout", "GET" ) ).logoutSuccessUrl( "/" ).deleteCookies( "JSESSIONID", "user", "login" ).invalidateHttpSession( true ).clearAuthentication( true ); http.headers().frameOptions().sameOrigin(); http.rememberMe().key( "uniqueAndSecret" ); http.sessionManagement().invalidSessionUrl( "/login.html" ); http.csrf().disable(); http.authenticationManager( authenticationManager(authManagerBuilder)); return http.build(); } @Bean public AuthenticationManager authenticationManager( AuthenticationManagerBuilder authManagerBuilder ) throws Exception { log.info( "DO some Authentication Manger things" ); return (AuthenticationManager)authManagerBuilder.authenticationProvider(activeDirectoryLdapAuthenticationProvider()).userDetailsService( userDetailsService).and.build(); } @Bean public AuthenticationProvider activeDirectoryLdapAuthenticationProvider() { log.info( "Authenticate: "+AD_DOMAIN+", "+AD_URL ); ActiveDirectoryLdapAuthenticationProvider provider = new ActiveDirectoryLdapAuthenticationProvider( AD_DOMAIN, AD_URL ); provider.setConvertSubErrorCodesToExceptions( true ); provider.setUseAuthenticationRequestCredentials( true ); provider.setSearchFilter( SEARCH_FILTER ); return provider; } }
MyUserDetailService类
@Service @Transactional public class MyUserDetailService implements UserDetailsService { @Override public UserDetails loadUserByUsername( String username ) throws UsernameNotFoundException { return null; } }
可能原因分析
ComponentScan扫描范围不匹配:WebSecurityConfigAD上的
@ComponentScan未指定扫描包,默认仅扫描当前类所在包及其子包。如果MyUserDetailService位于其他未被扫描的包下,Spring无法将其识别为Bean。此外,若项目主启动类使用@SpringBootApplication(自带@ComponentScan),WebSecurityConfigAD上的@ComponentScan会覆盖默认扫描规则,导致主启动类的扫描范围失效。Bean初始化顺序问题:在
filterChain方法中直接调用authenticationManager(authManagerBuilder)创建AuthenticationManager,此时MyUserDetailService可能尚未完成初始化,导致Autowired注入失败。冗余配置冲突:
ActiveDirectoryLdapAuthenticationProvider本身已内置用户认证逻辑,若无需自定义UserDetailsService的额外处理,强行配置反而可能引发依赖问题,但此点不影响Bean的识别。
解决方案
方案1:修正ComponentScan扫描范围
- 移除冗余的@ComponentScan:如果项目主启动类已使用
@SpringBootApplication,直接删除WebSecurityConfigAD上的@ComponentScan注解,让Spring默认扫描主启动类所在包及子包。 - 明确指定扫描包:若必须保留
@ComponentScan,需明确包含MyUserDetailService所在的包,例如:
@ComponentScan(basePackages = {"com.yourproject.service", "com.yourproject.config"})
方案2:调整AuthenticationManager配置逻辑
避免手动在filterChain中触发AuthenticationManager创建,交由Spring自动管理Bean生命周期:
- 修改
filterChain方法,删除http.authenticationManager(authenticationManager(authManagerBuilder));这一行。 - 调整
authenticationManager方法,改为注入AuthenticationManagerBuilder而非传入参数:
@Bean public AuthenticationManager authenticationManager(AuthenticationManagerBuilder auth) throws Exception { log.info("DO some Authentication Manger things"); auth.authenticationProvider(activeDirectoryLdapAuthenticationProvider()) .userDetailsService(userDetailsService); return auth.build(); }
Spring会自动将该AuthenticationManager注入到SecurityFilterChain中,避免初始化顺序问题。
方案3:检查自定义UserDetailsService的有效性
虽然当前问题是Bean未被识别,但后续需修正loadUserByUsername方法的返回值(当前返回null会导致认证失败),例如从数据库加载用户权限信息:
@Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { // 示例:从数据库查询用户 User user = repo.findByUsername(username); if (user == null) { throw new UsernameNotFoundException("User not found: " + username); } // 构建UserDetails对象 return User.withUsername(user.getUsername()) .password(user.getPassword()) // AD认证后可忽略密码,此处仅为示例 .authorities(user.getRoles().stream().map(Role::getName).toArray(String[]::new)) .build(); }
内容的提问来源于stack exchange,提问作者Api

