Вызов любых частных (закрытых) запросов REST приводит к ошибке построения пути PKIX в Spring Security 6 (OAuth2ResourceServer)

Недавно я перешел на Spring Boot 3, Java 17 и Spring Security 6. У меня уже есть рабочий сервер авторизации, развернутый для тестовых и прод-сред, поэтому нет необходимости внедрять его с нуля внутри моего проекта. Осталось только правильно реализовать ресурсный сервер. До Spring Boot 3 я использовал OAuth 2, но недавно узнал, что OAuth2 полностью устарел и его разработка была перенесена в Spring Security 6.
Когда я запускал свой проект, поначалу я столкнулся со многими проблемами, поэтому мне пришлось удалить множество устаревших библиотек, функций и т. д.

У меня есть следующий исходный код для моего сервера ресурсов:

@Configuration
@EnableWebSecurity
public class ResourceServerConfiguration {
    @Value("${spring.profiles.active}")
    private String activeProfile;

    @Value("${root.url.osb.basic.auth}")
    private String osbBasicAuth;

    @Value("${auth.server.url}")
    private String oauthServerUrl;

    @Value("${auth.server.clientId}")
    private String clientId;

    @Value("${auth.server.clientsecret}")
    private String clientSecret;


    public static final String BEARER_PREFIX = "Bearer ";
    public static final String HEADER_NAME = "Authorization";
    private final UserService userService;

    public ResourceServerConfiguration(UserService userService) {
        this.userService = userService;
    }

    @Bean
    public SecurityFilterChain configure(HttpSecurity http) throws Exception {
        http.addFilterAfter(new OncePerRequestFilter() {
            @Override
            protected void doFilterInternal(HttpServletRequest request,
                                            HttpServletResponse response,
                                            FilterChain filterChain)
                    throws ServletException, IOException {
                // We don't want to allow access to a resource with no token so clear
                // the security context in case it is actually an OAuth2Authentication
                var authHeader = request.getHeader(HEADER_NAME);
                if (StringUtils.isEmpty(authHeader) || !StringUtils.startsWith(authHeader, BEARER_PREFIX)) {
                    SecurityContextHolder.clearContext();
                }
                filterChain.doFilter(request, response);
            }
        }, AbstractPreAuthenticatedProcessingFilter.class);
//        http.csrf().disable();
        http.authorizeHttpRequests(authorize -> authorize.requestMatchers("/", "/v1/open/**").permitAll());
        http.authorizeHttpRequests(authorize -> authorize.requestMatchers("/pension/**").authenticated())
            .oauth2ResourceServer(oauth2 -> oauth2.opaqueToken(opaqueTokenConfigurer ->
                opaqueTokenConfigurer.introspectionUri(oauthServerUrl)
                    .introspectionClientCredentials(clientId, clientSecret)));

        if (StringUtils.equalsIgnoreCase(activeProfile, "dev")) {
            http.authorizeHttpRequests(authorize ->
                authorize.requestMatchers("/webjars/**", "/resources/**", "/swagger-ui.html", "/swagger-resources/**", "/v2/api-docs", "index.html")
                .permitAll());
        }
        http.authorizeHttpRequests(authorize -> authorize.anyRequest().authenticated())
            .oauth2ResourceServer(oauth2 -> oauth2.opaqueToken(opaqueTokenConfigurer ->
                opaqueTokenConfigurer.introspectionUri(oauthServerUrl)
                    .introspectionClientCredentials(clientId, clientSecret)));
        return http.build();
    }

    @Bean
    public UserDetailsService userDetailsService() {
        String[] authParts = osbBasicAuth.split(":");
        String username = authParts[0];
        String password = authParts[1];

        UserDetails userDetails = User.builder()
                .username(username)
                .password(new BCryptPasswordEncoder().encode(password)).roles()
                .build();
        return new InMemoryUserDetailsManager(userDetails);
    }
}

Как только я получаю токен доступа с сервера авторизации и пытаюсь отправить запрос, выдается следующая ошибка:

Caused by: org.springframework.web.client.ResourceAccessException: I/O error on POST request for "https://blabla/dev/agent-auth/oauth/": PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
    at org.springframework.web.client.RestTemplate.createResourceAccessException(RestTemplate.java:915)
    at org.springframework.web.client.RestTemplate.doExecute(RestTemplate.java:895)
    at org.springframework.web.client.RestTemplate.exchange(RestTemplate.java:740)
    at org.springframework.security.oauth2.server.resource.introspection.SpringOpaqueTokenIntrospector.makeRequest(SpringOpaqueTokenIntrospector.java:151)
    ... 75 common frames omitted

Раньше я решал эту проблему, когда использовал Spring boot 2 и Java 8, просто добавляя сертификаты в хранилище ключей, но даже в этот момент проблема не исчезла. В конце концов выяснилось, что проблема заключалась в том, что я добавлял сертификаты в хранилище ключей не в том месте. В этот момент я дважды проверил место добавления сертификатов, и когда я попытался отправить запрос, проблема все еще существует.

Местоположение JDK
Я добавляю все сертификаты в cacerts в этом месте.

Когда я использую .httpBasic(Customizer.withDefaults()) вместо oauth2ResourceServer и отправляю запросы, журналы ничего не показывают.
В Spring Boot 2 раньше существовали такие вещи, как RemoteTokenServices, которые, как я считаю, помогали автоматически решать проблемы при отправке запросов на сервер ресурсов с токенами доступа, но в Spring Boot 3, Spring Security 6 информации о них очень мало. правильные способы реализации аутентификации.

Такие руководства, как https://github.com/spring-projects/spring-security/wiki/OAuth-2.0-Migration-Guide
кажется, не дает много подробностей, и они неоднозначны для понимания.
Кроме того, во всех руководствах рассказывается, как реализовать аутентификацию с использованием JWT, но в этом руководстве я хочу реализовать ее с использованием простого токена Bearer, такого как аутентификация токена Opaque: https://docs.spring.io/spring-security/reference/servlet /oauth2/resource-server/opaque-token.html#oauth2resourceserver-opaque-introspectionuri-dsl

Вот почему, я прошу вашей помощи! Любая помощь будет оценена по достоинству. Спасибо!

Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Версия Java на основе версии загрузки
Версия Java на основе версии загрузки
Если вы зайдете на официальный сайт Spring Boot , там представлен start.spring.io , который упрощает создание проектов Spring Boot, как показано ниже.
Документирование API с помощью Swagger на Springboot
Документирование API с помощью Swagger на Springboot
В предыдущей статье мы уже узнали, как создать Rest API с помощью Springboot и MySql .
0
0
90
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

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

После долгого времени решения этой проблемы мне удалось ее решить, заменив URL-адрес, к которому я обращаюсь, и экспортировав сертификат доверия этого URL-адреса в хранилище доверенных сертификатов. Если исходный URL-адрес был https://blabla/dev/agent-auth/oauth/, позже я заменил его на https://blabla/dev/agent-auth/oauth/check_token, который является URL-адресом для проверки токена в моем проекте. Оказалось, что вся неразбериха заключалась в неправильном URL-адресе, который я использовал.

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

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