Spring Boot - ›UnsatisfiedDependencyException

Я пытаюсь активировать безопасность с помощью Oauth2 + JWT, но мне это не удается. Когда я пытаюсь запустить приложение в Spring Boot, оно возвращает эту ошибку: неудовлетворенная зависимость, выраженная через поле «authenticationManager», я поискал в Интернете и не нашел что-то, что могло бы мне помочь.

Я использую Spring Boot: 2.1.0.RELEASE

следите за моими занятиями:

Сервер ресурсов:

@Configuration
@EnableWebSecurity
@EnableResourceServer
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {

    @Autowired
    public void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.inMemoryAuthentication()
            .withUser("us3r").password("pwd").roles("ROLE");
    }

    @Override
    public void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                .antMatchers("/categorias").permitAll()
                .anyRequest().authenticated()
                .and()
            .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
            .csrf().disable();
    }

    @Override
    public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
        resources.stateless(true);
    }

}

Сервер авторизации:

@Configuration
@EnableAuthorizationServer
@Component
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {

    @Autowired
    @Qualifier("authenticationManagerBean")
    private AuthenticationManager authenticationManager;

    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        clients.inMemory().withClient("mobile").secret("p@sswd").scopes("read", "write")
                .authorizedGrantTypes("password", "refresh_token").accessTokenValiditySeconds(20)
                .refreshTokenValiditySeconds(3600 * 24);
    }

    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        endpoints.tokenStore(tokenStore()).accessTokenConverter(accessTokenConverter()).reuseRefreshTokens(false)
                .authenticationManager(authenticationManager);
    }

    @Bean
    public JwtAccessTokenConverter accessTokenConverter() {
        JwtAccessTokenConverter accessTokenConverter = new JwtAccessTokenConverter();
        accessTokenConverter.setSigningKey("algaworks");
        return accessTokenConverter;
    }

    @Bean
    public TokenStore tokenStore() {
        return new JwtTokenStore(accessTokenConverter());
    }

}

Трассировки стека:

org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'authorizationServerConfig': Unsatisfied dependency expressed through field 'authenticationManager'; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'org.springframework.security.authentication.AuthenticationManager' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {@org.springframework.beans.factory.annotation.Autowired(required=true), @org.springframework.beans.factory.annotation.Qualifier(value=authenticationManagerBean)}
    at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject(AutowiredAnnotationBeanPostProcessor.java:596) ~[spring-beans-5.1.2.RELEASE.jar:5.1.2.RELEASE]
    at org.springframework.beans.factory.annotation.InjectionMetadata.inject(InjectionMetadata.java:90) ~[spring-beans-5.1.2.RELEASE.jar:5.1.2.RELEASE]
    at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.postProcessProperties(AutowiredAnnotationBeanPostProcessor.java:374) ~[spring-beans-5.1.2.RELEASE.jar:5.1.2.RELEASE]
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.populateBean(AbstractAutowireCapableBeanFactory.java:1378) ~[spring-beans-5.1.2.RELEASE.jar:5.1.2.RELEASE]
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:575) ~[spring-beans-5.1.2.RELEASE.jar:5.1.2.RELEASE]
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:498) ~[spring-beans-5.1.2.RELEASE.jar:5.1.2.RELEASE]
    at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:320) ~[spring-beans-5.1.2.RELEASE.jar:5.1.2.RELEASE]
    at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:222) ~[spring-beans-5.1.2.RELEASE.jar:5.1.2.RELEASE]
    at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:318) ~[spring-beans-5.1.2.RELEASE.jar:5.1.2.RELEASE]
    at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:199) ~[spring-beans-5.1.2.RELEASE.jar:5.1.2.RELEASE]
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:846) ~[spring-beans-5.1.2.RELEASE.jar:5.1.2.RELEASE]
    at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:863) ~[spring-context-5.1.2.RELEASE.jar:5.1.2.RELEASE]
    at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:546) ~[spring-context-5.1.2.RELEASE.jar:5.1.2.RELEASE]
    at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.refresh(ServletWebServerApplicationContext.java:140) ~[spring-boot-2.1.0.RELEASE.jar:2.1.0.RELEASE]
    at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:775) [spring-boot-2.1.0.RELEASE.jar:2.1.0.RELEASE]
    at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:397) [spring-boot-2.1.0.RELEASE.jar:2.1.0.RELEASE]
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:316) [spring-boot-2.1.0.RELEASE.jar:2.1.0.RELEASE]
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:1260) [spring-boot-2.1.0.RELEASE.jar:2.1.0.RELEASE]
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:1248) [spring-boot-2.1.0.RELEASE.jar:2.1.0.RELEASE]
    at com.example.algamoney.api.AlgamoneyApiApplication.main(AlgamoneyApiApplication.java:10) [classes/:na]
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:1.8.0_181]
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[na:1.8.0_181]
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:1.8.0_181]
    at java.lang.reflect.Method.invoke(Method.java:498) ~[na:1.8.0_181]
    at org.springframework.boot.devtools.restart.RestartLauncher.run(RestartLauncher.java:49) [spring-boot-devtools-2.1.0.RELEASE.jar:2.1.0.RELEASE]
Caused by: org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'org.springframework.security.authentication.AuthenticationManager' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {@org.springframework.beans.factory.annotation.Autowired(required=true), @org.springframework.beans.factory.annotation.Qualifier(value=authenticationManagerBean)}
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.raiseNoMatchingBeanFound(DefaultListableBeanFactory.java:1646) ~[spring-beans-5.1.2.RELEASE.jar:5.1.2.RELEASE]
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1205) ~[spring-beans-5.1.2.RELEASE.jar:5.1.2.RELEASE]
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1166) ~[spring-beans-5.1.2.RELEASE.jar:5.1.2.RELEASE]
    at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject(AutowiredAnnotationBeanPostProcessor.java:593) ~[spring-beans-5.1.2.RELEASE.jar:5.1.2.RELEASE]
    ... 24 common frames omitted

person Mauro    schedule 29.11.2018    source источник
comment
Что вы получите без @Qualifier (authenticationManagerBean)?   -  person TinyOS    schedule 29.11.2018
comment
@SimonMartinelli Спасибо за ответ, удалил @Qualifier (authenticationManagerBean), но продолжает ту же ошибку   -  person Mauro    schedule 29.11.2018
comment
@TinyOS Спасибо за ответ, удалил @Qualifier (authenticationManagerBean), но продолжает ту же ошибку   -  person Mauro    schedule 29.11.2018


Ответы (2)


Причина, по которой вам нужно подключить AuthenticationManager, заключается в том, что вы используете password поток предоставления:

.authorizedGrantTypes("password", ...

С потоком предоставления пароля клиент OAuth2 предоставит вам свои учетные данные клиента, а также имя пользователя / пароль пользователя, и этот пользователь должен быть определен где-то в вашей системе. Возможность аутентифицировать этих пользователей предоставляется через экземпляр AuthenticationManager.

На сервере авторизации есть два набора пользователей: клиенты OAuth2 API и конечные пользователи приложения (люди, которые хотят войти в систему).

клиенты определены в классе, который расширяет AuthorizationServerConfigurerAdapter, который у вас есть. Вы определяете их при настройке ClientDetailsServiceConfigurer.

Пользователи определены в классе, расширяющем WebSecurityConfigurerAdapter, которого у вас нет. Вы можете определить их и предоставить соответствующий AuthenticationManager bean следующим образом (обратите внимание, что это ваша основная проблема, но есть дополнительные проблемы с вашей конфигурацией, поэтому читайте дальше):

@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    @Bean
    public UserDetailsService userDetailsService() {
        return new InMemoryUserDetailsManager(
            User.withDefaultPasswordEncoder()
                .username("us3r")
                .password("pwd")
                .roles("USER")
                .build());
    }

    @Override
    @Bean
    public AuthenticationManager authenticationManagerBean()
        throws Exception {
        return super.authenticationManagerBean();
    }
}

Этот последний метод предоставляет bean-компонент AuthenticationManager, так что другие bean-компоненты Spring могут зависеть от него (например, ваш bean-компонент AuthorizationServerConfig).

Давайте также немного очистим ваши другие классы, чтобы уменьшить шум, на случай, если вы столкнетесь с дополнительными препятствиями.

Во-первых, AuthorizationServerConfig. Избыточно использовать @Configuration и @Component. Вы можете просто использовать @Configuration. Кроме того, это может показаться лишней работой, но также полезно использовать хорошие пробелы с Spring Security DSL. Из-за свободного API он очень мощный, но его очень легко сделать нечитаемым.

Кроме того, когда вы переходите к производству, это будет казаться немного более естественным, но вам нужно указать механизм хеширования, который вы используете для пароля клиента. Поскольку сейчас вы не выполняете никакого хеширования, это может показаться немного странным, но вам нужно будет добавить к нему" {noop} ", чтобы этот образец работал.

И, наконец, 20 секунд - это довольно мало для токена доступа. Я бы рекомендовал хотя бы пару минут, хотя многие токены доступа действуют несколько часов. И если вы хотите, чтобы вашему пользователю приходилось повторно входить в систему каждый день, тогда для токена обновления достаточно 24 часов. По истечении срока действия токена обновления пользователю потребуется снова войти в систему со своими учетными данными.

Основываясь на вашей существующей конфигурации, я бы порекомендовал (хотя следите за обновлениями, если у вас возникнут другие проблемы с этой настройкой):

@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfig
    extends AuthorizationServerConfigurerAdapter {

    @Autowired
    @Qualifier("authenticationManagerBean")
    private AuthenticationManager authenticationManager;

    @Override
    public void configure(ClientDetailsServiceConfigurer clients)
        throws Exception {
        clients.inMemory()
            .withClient("mobile")
                .secret("{noop}p@sswd")
                .scopes("read", "write")
                .authorizedGrantTypes("password", "refresh_token")
                .accessTokenValiditySeconds(180)
                .refreshTokenValiditySeconds(3600 * 24);
    }

    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) 
        throws Exception {
        endpoints  
            .tokenStore(tokenStore())
            .accessTokenConverter(accessTokenConverter())
            .reuseRefreshTokens(false)
            .authenticationManager(authenticationManager);
    }

    @Bean
    public JwtAccessTokenConverter accessTokenConverter() {
        JwtAccessTokenConverter accessTokenConverter = 
            new JwtAccessTokenConverter();
        accessTokenConverter.setSigningKey("algaworks");
        return accessTokenConverter;
    }

    @Bean
    public TokenStore tokenStore() {
        return new JwtTokenStore(accessTokenConverter());
    }
}

Я заметил, что имя вашего клиента mobile. Современная рекомендация для мобильных клиентов - использовать PKCE, который Spring Security пока не поддерживает. Не зная всего о вашем варианте использования (похоже, вы просто пытаетесь заставить все работать прямо сейчас), я только упомяну, что мобильные приложения не умеют хранить секреты, поэтому вам нужно будет выбрать типы грантов, которые не требуют, чтобы мобильное приложение хранило секреты клиента (и password наверняка действительно требует секретов клиента).

Хорошо, второй, ResourceServerConfig. Мы можем удалить @EnableWebSecurity, потому что это есть в нашем новом классе SecurityConfig. Кроме того, сервер ресурсов обычно не поддерживает набор пользователей (они есть на сервере авторизации), поэтому мы можем удалить первый метод configure.

Исходя из вашей существующей конфигурации, я бы рекомендовал:

@Configuration
@EnableResourceServer
public class ResourceServerConfig
    extends ResourceServerConfigurerAdapter {

    @Override
    public void configure(HttpSecurity http) throws Exception {
        http
            .authorizeRequests()
                .antMatchers("/categorias").permitAll()
                .anyRequest().authenticated()
                .and()
           .sessionManagement()
               .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
               .and()
            .csrf().disable();
    }

    @Override
    public void configure(ResourceServerSecurityConfigurer resources) 
        throws Exception {
        resources.stateless(true);
    }
}

На всякий случай я вставил все это в локальный проект в своей среде IDE, запустил его и смог сделать следующее:

curl --user "mobile:p@sswd" localhost:8080/oauth/token -dgrant_type=password -dusername=us3r -dpassword=pwd

Который вернулся с токеном:

{ "access_token":"eyJh...",
  "token_type":"bearer",
  "refresh_token":"eyJh...", 
  "expires_in":199,
  "scope":"read write",
  "jti":"09855c19-7a71-4314-bf8f-eb74689d158c" }

Тогда я могу:

export set TOKEN="eyJh..."

Если я тогда сделаю:

curl localhost:8080/yo

Я получаю 401, но если я включу токен:

curl -H "Authorization: Bearer $TOKEN" localhost:8080/yo

Я получаю 404!

Что, как я понимаю, немного антиклиматично, но эй, это показывает, что я прошел аутентификацию. :)

Теперь, когда я сказал все это, если вы все еще читаете, я также хотел бы отметить, что в зависимости от ваших потребностей Spring Security 5.1 предлагает упрощенный API OAuth 2.0. У него еще нет сервера авторизации, но есть сервер ресурсов и клиент на основе JWT. Я рекомендую вам взглянуть на эту функцию матрица, чтобы узнать, есть ли в новой версии те функции, которые вам нужны.

person jzheaux    schedule 30.11.2018

Попробуйте использовать bean-компонент authenticationManager, как показано ниже:

    @Bean
    @Override
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }
person Raheela Aslam    schedule 29.11.2018
comment
Когда я добавляю переданный мне фрагмент кода, я получаю следующую ошибку: метод authenticationManagerBean () не определен для типа AuthorizationServerConfigurerAdapter - person Mauro; 29.11.2018