无法获取使用Spring引导和基于Java的配置注入的UserDetailsManager
我有spring boot webapp,它使用基于Java的配置来配置JdbcUserDetailsManager:无法获取使用Spring引导和基于Java的配置注入的UserDetailsManager,java,spring,spring-security,autowired,Java,Spring,Spring Security,Autowired,我有spring boot webapp,它使用基于Java的配置来配置JdbcUserDetailsManager: @Configuration @EnableWebMvcSecurity public class SecurityConfig extends WebSecurityConfigurerAdapter { @Autowired protected DataSource dataSource; @Autowired public void c
@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()
中设置断点,这样我就知道该方法正在被调用。现在,我想在我的应用程序类中注入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: {}
但我知道,在调用应用程序
构造函数之前,JdbcUserDetailsManager正在被实例化。这是怎么回事?如何验证JdbcUserDetailsManager是否已在上下文中注册
更新:通过如下更改我的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());
});
}
}
向普利尼奥·潘塔莱昂致敬,因为他把我推向了正确的方向。不幸的是,我不能就悬赏一事发表评论。我还不清楚为什么
AuthenticationManagerBuilder
没有在上下文中将userdetails服务自动注册为Bean。如果有人能提供一个权威性的答案,说明为什么我必须提供一个getter,或者解释如何在没有getter的情况下让它工作(这让我觉得有点不舒服),我会为这个答案奖励奖金。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(允许注入),并将其用于身份验证。Spring在这里发挥了一些神奇的作用,以确保重复调用userDetailsManager()
(或任何其他bean定义)会反复返回相同的对象,而不是每次都创建新实例
我将您的方法名称从getUserDetailsManager()
更改为userDetailsManager()
。这个方法是一个bean定义,而不是getter,所以这就是原因。我还从@Bean
注释中删除了这个名称,因为Spring在这里自动使用方法名作为Bean名称
补充一些细节:
首先,调用jdbccuthentication()
会产生一个新的jdbccuserdetailsmanager
实例,但它完全是内部的(即,不是Spring管理的bean)。我们可以说,因为Spring会抱怨当有多个bean满足一次注射时。有关详细信息,请查看的源代码以及各种超类。基本上,您将看到,jdbccuthentication()
调用会产生一个内部详细信息管理器,对userDetailsService()的调用将取代它
其次,调用userDetailsService()
将丢弃jdbc身份验证()
配置。以下是来自AuthenticationManagerBuilder
的相关方法:
public <T extends UserDetailsService>
DaoAuthenticationConfigurer<AuthenticationManagerBuilder,T>
userDetailsService(T userDetailsService) throws Exception {
this.defaultUserDetailsService = userDetailsService;
return apply(
new DaoAuthenticationConfigurer<AuthenticationManagerBuilder,T>
(userDetailsService));
}
公共
DaoAuthenticationConfigurer
userDetailsService(T userDetailsService)引发异常{
this.defaultUserDetailsService=userDetailsService;
退货申请(
新的DaoAuthenticationConfigurer
(用户详细信息服务);
}
这就是为什么我们将JdbcUserDetailsManager
配置从jdbcAuthentication()。(调用jdbcAuthentication()
基本上为创建JdbcUserDetailsManager
提供了一个方便、流畅的界面,但是我们这里不需要它,因为我们已经有了JdbcUserDetailsManager
)现在有了更好的方法是重写websecurityConfigureAdapter.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)创建的UserDetails服务作为bean公开。通常,此方法仅应执行以下覆盖:
见上面的例子
要更改返回的实例,开发人员应该改为更改userDetailsService()
websecurityConfigureAdapter.configure(AuthenticationManagerBuilder)的Javadoc中也提到了此方法
如果我的问题很愚蠢,我提前向您道歉,但您不需要在configAuthentication方法上构造UserDetailsManager并将其作为bean返回吗?我如何确定AuthenticationManagerBuilder.jdbAuthentication()不会实例化另一个JdbcUserDetailsManager?感谢您的澄清。但这是否意味着在jdbcAuthentication()之后链接的配置将被丢弃?在这种情况下有必要调用jdbcAuthentication吗?简单多了…:)