Spring Boot -> UnsatisfiedDependencyException - PullRequest
0 голосов
/ 29 ноября 2018

Я пытаюсь активировать защиту с помощью 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());
    }

}

Stacktrace:

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

Ответы [ 2 ]

0 голосов
/ 30 ноября 2018

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

.authorizedGrantTypes("password", ...

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

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

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

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

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

Этот последний метод предоставляет боб AuthenticationManager, так что другие бины Spring могут зависеть от него (например, ваш бин 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 предлагает упрощенный OAuth 2.0 API,У него еще нет сервера авторизации, но у него есть JWT-ресурсный сервер и клиент.Я бы посоветовал вам взглянуть на матрицу функций , чтобы увидеть, есть ли у более новых компонентов функции, которые вам нужны.

0 голосов
/ 29 ноября 2018

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

    @Bean
    @Override
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...