UserDetailsService bean not found

Viewed 28

I'm trying to define my own UserDetailsService for api calls.

So here is my security conf :

@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfiguration {

    @Bean
    @Order(1)
    public SecurityFilterChain apiSecurityFilterChain(HttpSecurity http, @Qualifier("apiUserDetailsService") ApiUserDetailsService apiUserDetailsService) throws Exception {

        http.antMatcher("/api/**")
                .userDetailsService(apiUserDetailsService)
                .csrf().disable()
                .cors(Customizer.withDefaults())
                .exceptionHandling(configurer -> configurer.authenticationEntryPoint(new AuthenticationFallbackEntryPoint()))
                .sessionManagement(configurer -> configurer.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
                .authorizeHttpRequests(authorize -> authorize.anyRequest().authenticated())
                .httpBasic(Customizer.withDefaults());

        return http.build();
    }

I expect the "ApiUserDetailsService" bean to be scanned and constructor-injected in my apiSecurityFilterChain.

Here is my ApiSecurityFilterChain (this is the "test" version) :

@Component
@Primary
@Qualifier("apiUserDetailsService")
public class ApiUserDetailsService implements UserDetailsService {
    
    @Override
    public UserDetails loadUserByUsername(String nni) {
        // (...)
        return new ApiUserDetails(apiUserDto, authorities);
    }
}

Here is how I declare my AuthentTest (which worked fine before the introduction of this custom UserDetailsService) :

@WebMvcTest({ UserController.class })
@Import({ SecurityConfiguration.class, GrantedPortalRoleConverter.class, ApiUserDetailsService.class })

@TestInstance(Lifecycle.PER_CLASS)
class AuthenticationTest {

Here is the bad result I get when my test starts :

Caused by: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'apiSecurityFilterChain' defined in class path resource [fr/mycomp/dvs/configuration/SecurityConfiguration.class]: Unsatisfied dependency expressed through method 'apiSecurityFilterChain' parameter 1;
Caused by: org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'fr.mycomp.dvs.security.ApiUserDetailsService' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {@org.springframework.beans.factory.annotation.Qualifier(value="apiUserDetailsService")}

Which mistake am I doing which prevents ApiUserDetailsService bean instanciation or detection as candidate ?

UPDATE : I have to stay my webapp starts with no warning or error, only the JUnit test causes problems.

UPDATE 2 : I've changed to ApiUserDetailsService (my actual implementation) to UserDetailsService and first the message was exactly the same, like if SecurityConfiguration was never rebuilt, then it worked. I think I had multiple layers of problems preventing it to work.

1 Answers

You have wrong declaration of bean ApiUserDetailsService by using custom bean name

@Component("apiUserDetailsService")
public class ApiUserDetailsService implements UserDetailsService {
    
    @Override
    public UserDetails loadUserByUsername(String nni) {
        // (...)
        return new ApiUserDetails(apiUserDto, authorities);
    }
}

OR

@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfiguration {

    @Bean
    @Order(1)
    public SecurityFilterChain apiSecurityFilterChain(HttpSecurity http, ApiUserDetailsService apiUserDetailsService) throws Exception {

        http.antMatcher("/api/**")
                .userDetailsService(apiUserDetailsService)
                .csrf().disable()
                .cors(Customizer.withDefaults())
                .exceptionHandling(configurer -> configurer.authenticationEntryPoint(new AuthenticationFallbackEntryPoint()))
                .sessionManagement(configurer -> configurer.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
                .authorizeHttpRequests(authorize -> authorize.anyRequest().authenticated())
                .httpBasic(Customizer.withDefaults());

        return http.build();
    }



      @Bean
      public ApiUserDetailsService apiUserDetailsService(){
            return new ApiUserDetailsService();
      }
}

public class ApiUserDetailsService implements UserDetailsService {
    
    @Override
    public UserDetails loadUserByUsername(String nni) {
        // (...)
        return new ApiUserDetails(apiUserDto, authorities);
    }
}

Related