无法让UserDetailsManager注入Spring Boot和基于Java的配置 [英] Cannot get UserDetailsManager injected with Spring Boot and Java-based Configuration

查看:1596
本文介绍了无法让UserDetailsManager注入Spring Boot和基于Java的配置的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有spring boot webapp,它使用基于Java的配置来配置JdbcUserDetailsManager:

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

}

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

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

但我知道JdbcUserDetailsManager在<$之前被实例化了c $ c>应用程序调用构造函数。这里发生了什么?如何验证JdbcUserDetailsManager实际上是否已在上下文中注册?

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?

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

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

}

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

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.

推荐答案

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

Spring injects beans, so you have to have a bean on the context for the injection to occur.

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

But don't create the bean in the configAuthentication() method. Create it in its own method, and then reference that from 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());
}

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

Now userDetailsManager() produces a properly-configured bean (allowing injection) and you're using 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 every time.

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

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

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

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

First, the call to jdbcAuthentication() results in a new JdbcUserDetailsManager instance, but it's wholly internal (i.e., not a Spring-managed bean). We can tell because Spring complains when there are multiple beans satisfying a single injection. For details, look at the source code for AuthenticationManagerBuilder, JdbcUserDetailsManagerConfigurer and various superclasses. Basically what you will see is that the jdbcAuthentication() call results in an internal details manager, which the call to userDetailsService() replaces.

其次,调用 userDetailsS​​ervice()丢弃 jdbcAuthentication()配置。以下是来自 AuthenticationManagerBuilder 的相关方法:

Second, calling userDetailsService() discards the jdbcAuthentication() configuration. Here's the relevant 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));
}

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

That's why we've moved the JdbcUserDetailsManager configuration out of the jdbcAuthentication() piece and into the userDetailsManager() method itself. (The jdbcAuthentication() call basically exposes a convenient, fluent interface for creating the JdbcUserDetailsManager, but we don't need it here because we already have our JdbcUserDetailsManager.)

这篇关于无法让UserDetailsManager注入Spring Boot和基于Java的配置的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆