blog.broncotoxique.com

Juste another geek’s website

Spring 6 – Oauthenticated http call

Let’s say you have a Spring backend app, and you need to make some http(s) call from that backend to any web-service you want without configuring a global WebClient.

Doing that you’ll face three possibly to authenticate your http call:
– forward the token of the current user;
– get a new token to authenticate your call;
– forward the user token and get a new token as fallback;

Now how do you do that. If you like to forward the user token, it’s pretty easy, you just need to get the current user token from the Spring SecurityContextHolder. If you need a new token, you have to use the spring-security-oauth2-client in order to get a token from an Oauth2 IDP. I guess you figured that the third situation consists of trying the SecurityContextHolder, if no token, get a new one.

I choosed to write a Supplier interface just to freeze the token suppliing:

public interface TokenSupplier extends Supplier<OAuth2Token> {}

Now let’s talk about how to get the token from the SecurityContextHolder:

public class CurrentUserTokenSupplierImpl implements TokenSupplier {

  @Override
  public @Nullable OAuth2Token get() {
    if (SecurityContextHolder.getContext().getAuthentication() instanceof AbstractOAuth2TokenAuthenticationToken authentication) {
      return authentication.getToken();
    }
    return null;
  }

}

Now let’s talk about how to get a new token from the spring-security-oauth2-client:

public class Oauth2TokenSupplierImpl implements TokenSupplier {

  private OAuth2Token oauth2token;
  private ClientRegistration registration;
  private ReactiveOAuth2AuthorizedClientManager authorizedClientManager;

  // Oauth
  public Oauth2TokenSupplierImpl(final ClientRegistration registration) {
    this.registration = registration;

    ReactiveClientRegistrationRepository clientRegistrationRepository = new InMemoryReactiveClientRegistrationRepository(registration);
    ReactiveOAuth2AuthorizedClientService clientService = new InMemoryReactiveOAuth2AuthorizedClientService(clientRegistrationRepository);
    ReactiveOAuth2AuthorizedClientProvider authorizedClientProvider = ReactiveOAuth2AuthorizedClientProviderBuilder.builder().clientCredentials().build();
    authorizedClientManager = new AuthorizedClientServiceReactiveOAuth2AuthorizedClientManager(clientRegistrationRepository, clientService);

    ((AuthorizedClientServiceReactiveOAuth2AuthorizedClientManager) authorizedClientManager).setAuthorizedClientProvider(authorizedClientProvider);
  }

  private void refeshToken() {
    if (isNull(oauth2token) || TokenUtils.isValid(oauth2token)) {
      oauth2token = getAccessToken(authorizedClientManager);
    }
  }

  @Override
  public @Nullable OAuth2Token get() {
    refeshToken();
    return oauth2token;
  }

  public OAuth2AccessToken getAccessToken(ReactiveOAuth2AuthorizedClientManager manager) {
    OAuth2AuthorizeRequest req = OAuth2AuthorizeRequest.withClientRegistrationId(registration.getClientId()).principal("N/A").build();
    OAuth2AuthorizedClient client = manager.authorize(req).block();
    return client.getAccessToken();
  }
}

Now, how to use this token into the http call, since we’re into the Spring universe, we will use the WebClien that will give the possibility to make some synchron or reactive call.

public class ClientBuilder {

  private static TokenSupplier getTokenSupplier(Properties props) {
    return switch (props.getStrategy()) {
      case NEW_TOKEN -> new Oauth2TokenSupplierImpl(props.getClientRegistration());
      case CURRENT_USER -> new CurrentUserTokenSupplierImpl();
      case CURRENT_NEW -> new FallbackTokenSupplier(new Oauth2TokenSupplierImpl(props.getClientRegistration()));
      case NO_OAUTH -> new NoTokenSupplier();
      default -> throw new IllegalArgumentException("Unexpected value: Strategy unknown");
    };
  }

  private static Consumer<HttpHeaders> oauthHeadersConsumer(GraphClientProperties props) {
    TokenSupplier tokenSupplier = getTokenSupplier(props);
    return headerConsumer -> headerConsumer.setBearerAuth(tokenSupplier.get().getTokenValue());
  }

  public static WebClient builder(Properties props) {
    Function<ClientRequest, Mono<ClientRequest>> oauthProcessor = cr -> Mono.just(ClientRequest.from(cr).headers(oauthHeadersConsumer(props)).build());

    return WebClient.builder().filter(ExchangeFilterFunction.ofRequestProcessor(oauthProcessor)).build();
  }
public class TokenUtils {
  private TokenUtils() {}
  public static boolean isValid(OAuth2Token oauth2token) {
    return Instant.now().isBefore(oauth2token.getExpiresAt());
  }
}

If you’d like an better Spring integration you should have a look here : https://blog.broncotoxique.com/2023/07/22/spring-boot-3-register-an-oidc-token-provider/


Posted

in

by

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *