Проблема Spring Security с JWT: невозможно создать подкласс финального класса JwtAuthenticationProvider

Буду признателен за вашу помощь в решении этой проблемы. У меня есть приложение (appA), разработанное с помощью Spring-Boot 3.2.5. В этом приложении есть еще один модуль (moduleA), который реализует сервер ресурсов Spring Security (spring-boot-starter-oauth2-resource-server). Всякий раз, когда я пытаюсь запустить приложение, я получаю эту ошибку:

Error creating bean with name
'org.springframework.security.config.annotation.web.configuration.WebSecurityConfiguration':
Unsatisfied dependency expressed through method 'setFilterChains'
parameter 0: Error creating bean with name
'oAuth2ResourceServerFilterChain' defined in class path resource
[com/modulea/oauth2resourceserver/config/Oauth2ResourceServerConfig.class]:
Failed to instantiate
[org.springframework.security.web.SecurityFilterChain]: Factory method
'oAuth2ResourceServerFilterChain' threw exception with message: Could
not postProcess
 org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationProvider@26f1b53b of type class
org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationProvider

Caused by: org.springframework.beans.factory.BeanCreationException:
Error creating bean with name 'oAuth2ResourceServerFilterChain'
defined in class path resource
[oauth2resourceserver/config/Oauth2ResourceServerConfig.class]: Failed
to instantiate [org.springframework.security.web.SecurityFilterChain]:
Factory method 'oAuth2ResourceServerFilterChain' threw exception with
message: Could not postProcess

org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationProvider@26f1b53b of type class
 org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationProvider
    at
 org.springframework.beans.factory.support.ConstructorResolver.instantiate(ConstructorResolver.java:648)
~[spring-beans-6.1.6.jar:6.1.6]

Project Structure

1. AppA's pom

    <dependency>
      <groupId>com.modulea</groupId>
      <artifactId>oauth2-resourceserver</artifactId>
      <version>0.0.1-SNAPSHOT</version>
    </dependency>

2. Module A
 - src
   - main
     - com
       - modulea
         - oauth2resourceserver
           - config
             - Oauth2ResourceServerConfig
             - RsaKeyProperties
             - DecoderEncoderConfig

Oauth2ResourceServerConfig

    package com.modulea.oauth2resourceserver.config;
    
    import lombok.AccessLevel;
    import lombok.RequiredArgsConstructor;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.core.annotation.Order;
    import org.springframework.security.config.Customizer;
    import org.springframework.security.config.annotation.web.builders.HttpSecurity;
    import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
    
    import org.springframework.security.config.http.SessionCreationPolicy;
    import org.springframework.security.oauth2.jwt.JwtDecoder;
    import org.springframework.security.web.SecurityFilterChain;
    import org.springframework.security.web.savedrequest.HttpSessionRequestCache;
    
    @RequiredArgsConstructor(access = AccessLevel.PROTECTED, onConstructor_ = {@Autowired})
    @Configuration
    public class Oauth2ResourceServerConfig {
        private final JwtDecoder jwtDecoder;
    
        @Order(4)
        @Bean
        public SecurityFilterChain oAuth2ResourceServerFilterChain(HttpSecurity http) throws Exception {
            HttpSessionRequestCache requestCache = new HttpSessionRequestCache();
            requestCache.setMatchingRequestParameterName(null);
            http
                    .csrf(AbstractHttpConfigurer::disable)
                    .authorizeHttpRequests(authorize -> authorize
                            .requestMatchers("/internal/**")
                                    .permitAll()
                            .anyRequest()
                                    .authenticated())
                    .oauth2ResourceServer(oAuth2ResourceServerConfigurer -> oAuth2ResourceServerConfigurer.jwt(Customizer.withDefaults()))
                    .sessionManagement(sessionManagement -> sessionManagement
                            .sessionCreationPolicy(SessionCreationPolicy.STATELESS))
                    .httpBasic(Customizer.withDefaults());
            return http
                    .build();
        }
    }

RsaKeyProperties

    package com.modulea.oauth2resourceserver.config;
    
    import org.springframework.boot.context.properties.ConfigurationProperties;
    
    import java.security.interfaces.RSAPrivateKey;
    import java.security.interfaces.RSAPublicKey;
    
    @ConfigurationProperties(prefix = "rsa")
    public record RsaKeyProperties(RSAPublicKey publicKey, RSAPrivateKey privateKey) {
    }

DecoderEncoderConfig

    package com.modulea.oauth2resourceserver.config;
    
    import com.nimbusds.jose.jwk.JWK;
    import com.nimbusds.jose.jwk.JWKSet;
    import com.nimbusds.jose.jwk.RSAKey;
    import com.nimbusds.jose.jwk.source.ImmutableJWKSet;
    import com.nimbusds.jose.jwk.source.JWKSource;
    import com.nimbusds.jose.proc.SecurityContext;
    import lombok.RequiredArgsConstructor;
    import org.springframework.boot.context.properties.EnableConfigurationProperties;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.security.oauth2.jwt.JwtDecoder;
    import org.springframework.security.oauth2.jwt.JwtEncoder;
    import org.springframework.security.oauth2.jwt.NimbusJwtDecoder;
    import org.springframework.security.oauth2.jwt.NimbusJwtEncoder;
    
    @EnableConfigurationProperties({RsaKeyProperties.class})
    @RequiredArgsConstructor
    @Configuration
    public class DecoderEncoderConfig {
    
        private final RsaKeyProperties rsaKeyProperties;
    
        @Bean
        public JwtDecoder jwtDecoder() {
            return NimbusJwtDecoder.withPublicKey(rsaKeyProperties.publicKey()).build();
        }
    
        @Bean
        public JwtEncoder jwtEncoder() {
            JWK jwk = new RSAKey.Builder(rsaKeyProperties.publicKey()).privateKey(rsaKeyProperties.privateKey()).build();
            JWKSource<SecurityContext> jwkSource = new ImmutableJWKSet<>(new JWKSet(jwk));
            return new NimbusJwtEncoder(jwkSource);
        }
    }
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
В компьютерном программировании биты играют важнейшую роль в представлении и манипулировании данными на двоичном уровне. Побитовые операции...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Полный курс Java для разработчиков веб-сайтов и приложений
Полный курс Java для разработчиков веб-сайтов и приложений
Получите сертификат Java Web и Application Developer, используя наш курс.
2
0
88
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Проблема связана с тем, что прокси Spring CGLIB пытается создать в моем случае динамические прокси для bean-компонентов в классе конфигурации. Я решил изменить архитектуру, чтобы сделать сервер ресурсов автономным.

Отредактировано ниже:

После нескольких дней исследований эти ссылки помогли мне решить проблему без автономного сервера ресурсов. Проблема заключалась в том, что у меня уже был bean-компонент DaoAuthenticationProvider. Поэтому Spring Security отказалась от создания дополнительных bean-компонентов аутентификации. Если бы я ничего не создавал, то безопасность Spring подключила бы правильный компонент.

Ссылка: я. Уже существующая SecurityFilterChain не позволяет мне настроить Spring Security?

ii. https://docs.spring.io/spring-security/reference/servlet/authentication/architecture.html#servlet-authentication-authenticationprovider

Из ссылок, которыми я поделился, следует отметить следующие части:

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

Если вам нужен AuthenticationManager где-то еще (например, внедрённый в фильтр), измените определение bean-компонента на следующее:

@Bean
public AuthenticationManager authenticationManager(AuthenticationConfiguration authenticationConfiguration) throws Exception {
    return authenticationConfiguration.getAuthenticationManager();
}

Поставщик аутентификации Вы можете внедрить несколько экземпляров AuthenticationProviders в ProviderManager. Каждый AuthenticationProvider выполняет определенный тип аутентификации. Например, DaoAuthenticationProvider поддерживает аутентификацию на основе имени пользователя и пароля, а JwtAuthenticationProvider поддерживает аутентификацию токена JWT.

Итак, я добавил строку ниже, чтобы решить мою проблему:

@Bean
public JwtAuthenticationProvider jwtAuthenticationProvider(JwtDecoder jwtDecoder) {
    return new JwtAuthenticationProvider(jwtDecoder);
}

Другие вопросы по теме