Cannot Get UserDetailsManager Using Spring Boot and Java Configuration

I have a spring boot webapp that uses Java configuration to configure 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 the method is called. Now I want to get the JdbcUserDetailsManager introduced 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 run the 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 that JdbcUserDetailsManager gets an instance before calling the Application constructor. What's going on here? How can I verify that the JdbcUserDetailsManager is actually registered in context?

Update: By changing my SecurityConfig as follows, I was able to solve 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()); }); } } 

Head towards Plinio Pantaleo in order to push me in the right direction. Unfortunately, I cannot reward Bounty for the comment. I also still do not understand why AuthenticationManagerBuilder does not register UserDetailsService as a Bean in context automatically. If someone can give an authoritative answer to the question of why I should provide a getter, or can explain how to make it work without a getter (which seems a bit hacked to me), I am rewarded for this answer.

+6
source share
2 answers

Spring introduces beans, so you need to have a bean in the context for injection.

But do not create a bean in the configAuthentication() method. Create it in your own method, and then configAuthentication() it using the configAuthentication() method. Like this:

 @Bean public JdbcUserDetailsManager userDetailsManager() { JdbcUserDetailsManager manager = new JdbcUserDetailsManager(); manager.setDataSource(dataSource); manager.setUsersByUsernameQuery( "select username,password,enabled from users where username=?"); manager.setAuthoritiesByUsernameQuery( "select username, role from user_roles where username=?"); manager.setRolePrefix("ROLE_"); return manager; } @Autowired public void configAuthentication(AuthenticationManagerBuilder builder) throws Exception { builder.userDetailsService(userDetailsManager()); } 

Now userDetailsManager() creates a properly configured bean (allowing injection) and you use it for authentication. Spring does some magic here to ensure that repeated calls to userDetailsManager() (or any other bean definition) return the same object over and over, instead of creating new instances each time.

I changed the method name from getUserDetailsManager() to userDetailsManager() . This method is a bean definition, not a getter, so the reason. I also removed the name from the @Bean annotation, since Spring automatically uses the method name for the bean name here.

A few additional notes to fill in some details:

Firstly, calling jdbcAuthentication() creates a new instance of JdbcUserDetailsManager , but it is completely internal (i.e. not a Spring-installed bean). We can say that Spring complains when there are several beans satisfying a single injection. For more information, see AuthenticationManagerBuilder Source Code, JdbcUserDetailsManagerConfigurer, and various superclasses. Basically, you will see that calling jdbcAuthentication() leads to an internal data manager that replaces calling userDetailsService() .

Secondly, calling userDetailsService() discards the jdbcAuthentication() configuration. Here is the corresponding method from AuthenticationManagerBuilder :

 public <T extends UserDetailsService> DaoAuthenticationConfigurer<AuthenticationManagerBuilder,T> userDetailsService(T userDetailsService) throws Exception { this.defaultUserDetailsService = userDetailsService; return apply( new DaoAuthenticationConfigurer<AuthenticationManagerBuilder,T> (userDetailsService)); } 

That's why we moved the JdbcUserDetailsManager configuration from jdbcAuthentication() and to the userDetailsManager() method. (Calling jdbcAuthentication() basically provides a convenient, smooth interface for creating the JdbcUserDetailsManager , but it is not needed here because we already have our JdbcUserDetailsManager .)

+10
source

There is (now?) A better way to do this by overriding WebSecurityConfigurerAdapter.userDetailsServiceBean() and registering it as @Bean :

 public static class SecurityConfig extends WebSecurityConfigurerAdapter { @Bean(name = "myUserDetailsService") // any or no name specified is allowed @Override public UserDetailsService userDetailsServiceBean() throws Exception { return super.userDetailsServiceBean(); } } 

Javadoc:

Override this method to open a UserDetailsService created with configure (AuthenticationManagerBuilder) as a bean. In general, only the following override should be used for this method:

see example above

To change the returned instance, developers should change userDetailsService () instead

This method is also mentioned in the Javadoc WebSecurityConfigurerAdapter.configure(AuthenticationManagerBuilder)

0
source

Source: https://habr.com/ru/post/974713/


All Articles