具有密码授予类型的WebClient的Spring Security OAuth客户端不要求新令牌

时间:2020-07-10 14:51:49

标签: spring spring-security spring-webflux

我有一个Spring Web应用程序,该应用程序具有为其API端点配置的oauth2资源服务器,以及用于其进行的REST调用的完全不同的oauth2客户端。 oauth2客户端必须是密码授予类型。用户名和密码是固定的(不是来自HTTP请求)。我的问题是30分钟后访问令牌和刷新令牌都将过期,因此没有办法进行刷新。我希望Spring Security只会请求新的访问令牌,但不会。它用过期的端点调用REST端点,并返回403。 这就是我所拥有的:

application.yml:

spring:
  security:
    oauth2:
      resourceserver:
        jwt:
          issuer-uri: https://our.idp.keycloak.host/auth/realms/firstrealm
      client:
        registration:
          my-client-authorization:
            client-id: my_client
            client-secret: ${CLIENT_SECRET}
            authorization-grant-type: password
            scope: openid, profile
        provider:
          my-client-authorization:
            token-uri: https://our.idp.keycloak.host/auth/realms/secondrealm/protocol/openid-connect/token

MyClientConfig.java:

import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;
import org.springframework.security.oauth2.client.OAuth2AuthorizationContext;
import org.springframework.security.oauth2.client.OAuth2AuthorizedClientManager;
import org.springframework.security.oauth2.client.OAuth2AuthorizedClientProvider;
import org.springframework.security.oauth2.client.OAuth2AuthorizedClientProviderBuilder;
import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository;
import org.springframework.security.oauth2.client.web.DefaultOAuth2AuthorizedClientManager;
import org.springframework.security.oauth2.client.web.OAuth2AuthorizedClientRepository;
import org.springframework.security.oauth2.client.web.reactive.function.client.ServletOAuth2AuthorizedClientExchangeFilterFunction;
import org.springframework.web.reactive.function.client.WebClient;

import java.util.Map;

@Configuration
@RequiredArgsConstructor
public class MyClientConfig {
    @Bean
    WebClient webClient(OAuth2AuthorizedClientManager authorizedClientManager) {
        ServletOAuth2AuthorizedClientExchangeFilterFunction oauth2Client = new ServletOAuth2AuthorizedClientExchangeFilterFunction(authorizedClientManager);
        oauth2Client.setDefaultClientRegistrationId("my-client-authorization");
        return WebClient.builder()
                .apply(oauth2Client.oauth2Configuration())
                .baseUrl("https://the.api.host.to.call")
                .build();
    }

    @Bean
    public OAuth2AuthorizedClientManager authorizedClientManager(
            ClientRegistrationRepository clientRegistrationRepository,
            OAuth2AuthorizedClientRepository authorizedClientRepository
    ) {
        OAuth2AuthorizedClientProvider authorizedClientProvider = OAuth2AuthorizedClientProviderBuilder.builder()
                .password()
                .build();

        DefaultOAuth2AuthorizedClientManager result = new DefaultOAuth2AuthorizedClientManager(
                clientRegistrationRepository,
                authorizedClientRepository
        );

        result.setAuthorizedClientProvider(authorizedClientProvider);
        result.setContextAttributesMapper(oAuth2AuthorizeRequest -> Map.of(
                OAuth2AuthorizationContext.USERNAME_ATTRIBUTE_NAME, "user",
                OAuth2AuthorizationContext.PASSWORD_ATTRIBUTE_NAME, "password"
        ));

        return result;
    }
}

API调用本身:

private <T> T callApi(Function<UriBuilder, URI> uriFunction, Class<T> resultType) {
    return this.webClient
            .get()
            .uri(uriFunction)
            .retrieve()
            .bodyToMono(resultType)
            .block();
}

它在我第一次调用时有效。但是30分钟后,令牌已失效,我不知道如何获得一个新令牌。 如果我将其切换为client_credentials授予类型,它将起作用,并且在需要时会自动获取一个新令牌。但是由于某些原因,我不能对密码授予类型进行相同的操作。

编辑: 因此,由于以下原因,我设法解决了这个问题:1。 当我还为refreshToken配置WebClient时,刷新令牌过期时会崩溃。但是当您在此之后再次执行请求时,它将获得一个新令牌。因此,我不得不将API调用包装在try catch中,如果错误是我所关心的错误,我将再次调用该API。这不是一个非常优雅的解决方案,但它可以工作。

1 个答案:

答案 0 :(得分:0)

here中明确提到,Spring会在过期时自动刷新访问令牌。

我认为您需要使用过的“反应性”等效类。请尝试使用此代码段。

@Configuration
@RequiredArgsConstructor
public class MyClientConfig {
  @Bean
  WebClient webClient(ReactiveOAuth2AuthorizedClientManager authorizedClientManager) {
    ServerOAuth2AuthorizedClientExchangeFilterFunction oauth2Client = new ServerOAuth2AuthorizedClientExchangeFilterFunction(authorizedClientManager);
    oauth2Client.setDefaultClientRegistrationId("my-client-authorization");
    return WebClient.builder()
        .filter(oauth2Client)
        .baseUrl("https://the.api.host.to.call")
        .build();
  }

  @Bean
  public ReactiveOAuth2AuthorizedClientManager authorizedClientManager(
      ReactiveClientRegistrationRepository clientRegistrationRepository,
      ReactiveOAuth2AuthorizedClientService authorizedClientService
  ) {
    ReactiveOAuth2AuthorizedClientProvider authorizedClientProvider = ReactiveOAuth2AuthorizedClientProviderBuilder.builder()
        .password()
        .build();

    AuthorizedClientServiceReactiveOAuth2AuthorizedClientManager result = new AuthorizedClientServiceReactiveOAuth2AuthorizedClientManager(
        clientRegistrationRepository,
        authorizedClientService
    );

    result.setAuthorizedClientProvider(authorizedClientProvider);
    result.setContextAttributesMapper(oAuth2AuthorizeRequest -> Mono.just(Map.of(
        OAuth2AuthorizationContext.USERNAME_ATTRIBUTE_NAME, "user",
        OAuth2AuthorizationContext.PASSWORD_ATTRIBUTE_NAME, "password"
    )));

    return result;
  }
}