无法让UserDetailsManager注入Spring Boot和基于Java的配置

我有spring boot webapp,它使用基于Java的配置来配置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()); }); } } 

我可以在configAuthentication()设置断点,所以我知道该方法被调用。 我现在想要在我的Application类中注入JdbcUserDetailsManager

 @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; ... 

当我尝试启动我的应用程序时,出现以下错误:

 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: {} 

但我知道在调用Application构造函数之前,JdbcUserDetailsManager正在实例化。 这里发生了什么? 如何validationJdbcUserDetailsManager实际上是否已在上下文中注册?

更新:通过如下更改我的SecurityConfig ,我能够解决问题:

 @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()); }); } } 

前往PlínioPantaleão,让我朝着正确的方向前进。 不幸的是,我无法将赏金奖励给他们。 我还不清楚为什么AuthenticationManagerBuilder没有自动在上下文中将UserDetailsS​​ervice注册为Bean。 如果有人可以提供一个权威的答案,说明为什么我必须提供一个吸气剂, 或者可以解释如何让它在没有吸气剂的情况下工作(这对我来说有点不好意思),我将奖励这个答案的赏金。

Spring会注入bean,因此您必须在上下文中有一个bean才能进行注入。

但是不要在configAuthentication()方法中创建bean。 在它自己的方法中创建它,然后从configAuthentication()方法引用它。 喜欢这个:

 @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()); } 

现在userDetailsManager()生成一个正确配置的bean(允许注入),并且您正在使用它进行身份validation。 Spring在这里做了一些魔术,以确保重复调用userDetailsManager() (或任何其他bean定义)一遍又一遍地返回相同的对象,而不是每次都创建新的实例。

我将您的方法名称从getUserDetailsManager()更改为userDetailsManager() 。 这个方法是一个bean定义,而不是一个getter,所以这就是原因。 我也从@Bean注释中删除了名称,因为Spring在这里自动使用bean名称的方法名称。

补充一些细节的附加说明:

首先,对jdbcAuthentication()的调用会产生一个新的JdbcUserDetailsManager实例,但它完全是内部的(即,不是Spring管理的bean)。 我们可以告诉你,因为当有多个豆满足一次注射时,Spring会抱怨。 有关详细信息,请查看AuthenticationManagerBuilder , JdbcUserDetailsManagerConfigurer和各种超类的源代码。 基本上你会看到jdbcAuthentication()调用产生一个内部细节管理器,调用userDetailsService()替换它。

其次,调用userDetailsService()会丢弃jdbcAuthentication()配置。 这是AuthenticationManagerBuilder的相关方法:

 public  DaoAuthenticationConfigurer userDetailsService(T userDetailsService) throws Exception { this.defaultUserDetailsService = userDetailsService; return apply( new DaoAuthenticationConfigurer (userDetailsService)); } 

这就是为什么我们将JdbcUserDetailsManager配置从jdbcAuthentication()片段移到userDetailsManager()方法本身。 ( jdbcAuthentication()调用基本上公开了一个方便,流畅的接口来创建JdbcUserDetailsManager ,但是我们这里不需要它,因为我们已经有了JdbcUserDetailsManager 。)

通过重写WebSecurityConfigurerAdapter.userDetailsServiceBean()并将其注册为@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:

重写此方法以将configure(AuthenticationManagerBuilder)创建的UserDetailsS​​ervice公开为bean。 通常,只应对此方法执行以下覆盖:

见上面的例子

要更改返回的实例,开发人员应该更改userDetailsS​​ervice()

WebSecurityConfigurerAdapter.configure(AuthenticationManagerBuilder)的Javadoc中也提到了此方法