Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Cannot get UserDetailsManager injected with Spring Boot and Java-based Configuration

Tags:

I have spring boot webapp that uses the Java-based configuration to configure a JdbcUserDetailsManager:

@Configuration
@EnableWebMvcSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    protected DataSource dataSource;

    @Autowired
    public void configAuthentication(AuthenticationManagerBuilder auth) throws Exception {
      auth.jdbcAuthentication()                
        .dataSource(dataSource)                
        .usersByUsernameQuery("select username as principal, password as credentials, true from users where username = ?")               
        .authoritiesByUsernameQuery("select username as principal, authority as role from authorities where username = ?")                
        .rolePrefix("ROLE_");
    }   

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            .csrf().disable()
            .authorizeRequests()
                .antMatchers("/api/**")
                .authenticated()
            .and()
            .formLogin()
                .successHandler(
                    (request, response, authentication) -> {
                        response.setStatus(HttpStatus.NO_CONTENT.value());
                    })
                .failureHandler(
                    (request, response, authentication) -> {
                        response.setStatus(HttpStatus.FORBIDDEN.value());
                    })
            .and()
            .logout()
                .logoutUrl("/logout")
                .logoutSuccessHandler(
                    (request, response, authentication) -> {
                        response.setStatus(HttpStatus.NO_CONTENT.value());
                    }); 
    }

}

I can set a breakpoint in configAuthentication(), so I know that the method is getting called. I now want to get the JdbcUserDetailsManager injected in my Application class:

@EnableAutoConfiguration
@ComponentScan
public class Application {

    private Environment env;
    private UserDetailsManager userDetailsManager;

    @Autowired
    public Application(JdbcTemplate jdbcTemplate, Environment env, UserDetailsManager userDetailsManager) {
        this.env = env;
        this.userDetailsManager = userDetailsManager;
        ...

When I try to start my application, I get the following error:

org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'application': Unsatisfied dependency expressed through constructor argument with index 2 of type [org.springframework.security.provisioning.UserDetailsManager]: : No qualifying bean of type [org.springframework.security.provisioning.UserDetailsManager] found for dependency: expected at least 1 bean which qualifies as autowire candidate for this dependency. Dependency annotations: {}; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type [org.springframework.security.provisioning.UserDetailsManager] found for dependency: expected at least 1 bean which qualifies as autowire candidate for this dependency. Dependency annotations: {}

But I know for a fact that a JdbcUserDetailsManager is getting instantiated before the Application constructor is called. What's going on here? How can I validate that the JdbcUserDetailsManager is actually registered with the context?

Update: By changing my SecurityConfig as follows, I was able to resolve the problem:

@Configuration
@EnableWebMvcSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    protected DataSource dataSource;
    private JdbcUserDetailsManager userDetailsManager;

    @Autowired
    public void configAuthentication(AuthenticationManagerBuilder auth) throws Exception {
        this.userDetailsManager = auth.jdbcAuthentication().dataSource(dataSource)
            .usersByUsernameQuery(
                "select username,password,enabled from users where username=?")
            .authoritiesByUsernameQuery(
                "select username, role from user_roles where username=?").getUserDetailsService();
    }

    @Bean(name = "userDetailsManager")
    public JdbcUserDetailsManager getUserDetailsManager() {
        return userDetailsManager;
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            .csrf().disable()
            .authorizeRequests()
            .antMatchers("/api/**")
            .authenticated()
            .and()
            .formLogin()
            .successHandler(
                (request, response, authentication) -> {
                    response.setStatus(HttpStatus.NO_CONTENT.value());
                })
            .failureHandler(
                (request, response, authentication) -> {
                    response.setStatus(HttpStatus.FORBIDDEN.value());
                })
            .and()
            .logout()
            .logoutUrl("/logout")
            .logoutSuccessHandler(
                (request, response, authentication) -> {
                    response.setStatus(HttpStatus.NO_CONTENT.value());
                });
    }

}

Heads up to Plínio Pantaleão for pushing me in the right direction. Unfortunately, I cannot award the Bounty for a comment. I'm also still not clear on why the AuthenticationManagerBuilder does not register the UserDetailsService as a Bean in the context automatically. If anybody can provide an authoritative answer on why I have to provide a getter or can explain how to make it work without the getter (which feels somewhat hacky to me), I will award the bounty for that answer.