Обнаружена ошибка при реализации многопользовательского сервера ресурсов OAuth 2.0

#spring-security #spring-security-oauth2

Вопрос:

Я реализовал код, как указано здесь, чтобы добавить функцию множественной аренды по эмитенту в мою конфигурацию Spring Security. Однако при запуске моего приложения Spring Boot я сталкиваюсь со следующей ошибкой:

 2021-10-26 | 10:31:37.762 | main | WARN | ConfigServletWebServerApplicationContext | Trace: | Exception encountered during context initialization - cancelling refresh attempt: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'springSecurityFilterChain' defined in class path resource [org/springframework/security/config/annotation/web/configuration/WebSecurityConfiguration.class]: Bean instantiation via factory method failed; nested exception is org.springframework.beans.BeanInstantiationException: Failed to instantiate [javax.servlet.Filter]: Factory method 'springSecurityFilterChain' threw exception; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'org.springframework.security.oauth2.jwt.JwtDecoder' available
2021-10-26 | 10:31:39.361 | main | ERROR | o.s.b.d.LoggingFailureAnalysisReporter | Trace: |
***************************
APPLICATION FAILED TO START
***************************
Description:
Method springSecurityFilterChain in org.springframework.security.config.annotation.web.configuration.WebSecurityConfiguration required a bean of type 'org.springframework.security.oauth2.jwt.JwtDecoder' that could not be found.
Action:
Consider defining a bean of type 'org.springframework.security.oauth2.jwt.JwtDecoder' in your configuration.
 

В документации указано:

Это хорошо, потому что конечные точки эмитента загружаются лениво. Фактически, экземпляр соответствующего JwtAuthenticationProvider создается только при отправке первого запроса с соответствующим эмитентом.

Я бы не подумал, что при запуске приложения JwtDecoder ожидается, что экземпляр a будет уже создан в соответствии с этой документацией. Чего мне не хватает в моей конфигурации?

Обновить

После помощи Стива Ризенберга у меня теперь компилируется следующий код. Вы можете видеть в моем фрагменте кода, что у меня раньше работало (т. Е. До того, Как у нас было требование многопользовательского доступа), теперь закомментировано:

//.jwt().jwtAuthenticationConverter(jwtAccessTokenConverter);

 String[] issuers = new String[] {"https://www.example.com/auth/realms/example"};
JwtIssuerAuthenticationManagerResolver jwtIssuerAuthenticationManagerResolver =
    new JwtIssuerAuthenticationManagerResolver(issuers);
...
        .anyRequest()
        .authenticated()
        .and()
        .oauth2ResourceServer(
            oauth2ResourceServerConfigurer ->
                oauth2ResourceServerConfigurer
                    .authenticationManagerResolver(jwtIssuerAuthenticationManagerResolver)
                    .authenticationEntryPoint(authenticationExceptionHandler));
    // .jwt().jwtAuthenticationConverter(jwtAccessTokenConverter);
 

Однако, не имея возможности теперь предоставить мой собственный конвертер токенов, поскольку мне пришлось удалить .jwt() , мне все еще неясно, что предоставляет мне конвертер по умолчанию.

Кроме того, я не понимаю, зачем мне нужно использовать третий конструктор JwtIssuerAuthenticationManagerResolver и предоставлять свой собственный AuthenticationManagerResolver<String> ? Если мой приведенный выше код компилируется, зачем мне это делать?

Ответ №1:

Требуется JwtDecoder , если вы настроили сервер ресурсов с помощью a JwtAuthenticationProvider (потому что для этого требуется определенный JwtDecoder ). Это произойдет, если вы, например,:

 http
    ...
    .oauth2ResourceServer(oauth2 -> oauth2
        .authenticationManagerResolver(authenticationManagerResolver)
        .jwt(Customizer.withDefaults())
    )
 

Поскольку authenticationManagerResolver это альтернатива, которая разветвляется на AuthenticationManager уровне, вы не хотите использовать JwtAuthenticationProvider . Он будет использоваться внутри JwtIssuerAuthenticationManagerResolver .

Удалите .jwt() в этом случае, чтобы конфигуратор не подключал его.

Обновить

В разделе документов о динамических арендаторах содержится дополнительная информация о различных параметрах настройки.

В вашем случае без использования .jwt() вы не можете так же легко подключить a JwtAuthenticationConverter , который может настраивать возвращаемые предоставленные полномочия.

Внутри JwtIssuerAuthenticationManagerResolver используется a TrustedIssuerJwtAuthenticationManagerResolver . Это то, что выполняет функцию множественной аренды, извлекая issuer утверждение из JWT и создавая JwtDecoder new JwtAuthenticationProvider(jwtDecoder) на основе сопоставленного issuer .

Чтобы настроить JwtAuthenticationProvider , вам придется повторно реализовать этот класс, чтобы вы могли вводить свой JwtAuthenticationConverter в каждый созданный экземпляр. AuthenticationManagerResolver<String> Для этого необходимо выполнить реализацию. Вызовите его CustomTrustedIssuerJwtAuthenticationManagerResolver (см. Эту строку).

Вам просто нужно предоставить это JwtIssuerAuthenticationManagerResolver , например:

 String[] issuers = new String[] {"https://www.example.com/auth/realms/example"};
AuthenticationManagerResolver<String> authenticationManagerResolver =
    new CustomTrustedIssuerJwtAuthenticationManagerResolver(issuers);
JwtIssuerAuthenticationManagerResolver jwtIssuerAuthenticationManagerResolver =
    new JwtIssuerAuthenticationManagerResolver(authenticationManagerResolver);
...
 

Комментарии:

1. В настоящее время моя конфигурация содержит .jwt().jwtAuthenticationConverter(jwtAccessTokenConverter) . Этот конвертер представляет собой компонент, который реализует Converter<Jwt, AbstractAuthenticationToken> . Его задача состоит в том, чтобы извлечь основные и предоставленные полномочия из Jwt и return new JwtAuthenticationToken(jwt, authorities, principalName) . Если я удалю .jwt() , как вы предлагаете, то где я могу подключить этот компонент в моей конфигурации? В настоящее время jwtAuthenticationConverter метод может быть вызван только непосредственно после .jwt() метода.

2. Чтобы дать вам представление о том, почему я настроил этот преобразователь, я затем использую JwtAuthenticationToken для выполнения таких действий, как авторизация вызова конечной точки контроллера REST, используя @PreAuthorize разработанный мной пользовательский код, который оценивает предоставленные полномочия, содержащиеся в JwtAuthenticationToken .

3. Итак, часть, которую я упускаю, заключается в том, что при подходе с использованием JwtIssuerAuthenticationManagerResolver , как я могу получить доступ к экземпляру JwtAuthenticationToken , и где и как этот экземпляр настроен? Я надеюсь, что мое объяснение того, что я сейчас делаю и почему, понятно.

4. Мне нужно было бы проверить, работает ли он с 5.3 (если это версия, которую вы используете, по ссылке в документах), но вы можете попробовать добавить @Bean public JwtAuthenticationConverter jwtAuthenticationConverter() { ... } .

5. Мы используем Spring Cloud 2020.0.3 и Spring Boot 2.5.6. Итак, транзитивно через этот импорт pom мы используем Spring Security 5.5.3.