blog.broncotoxique.com

Juste another geek’s website

Spring-Boot 3 – register an OIDC token provider

Let say you have a Spring-Boot application and you need a token in order to authenticate/authorize a call made by your application targeting another app.

Since Spring-Boot 3, and Spring 6, the spring-security packages handle the OIDC mechanism with built-in code. Also their is a common part in registration of OIDC provider used to authenticate/authorize a call coming in the application and registration of OIDC provider to get a token to make some external call.

Here are the steps you’ll follow to get your calls with tokens.

Step zero you have to add those dependencies to your project.

<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-oauth2-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-oauth2-resource-server</artifactId>
</dependency>

First you choose a name for your OIDC provider, here it will be “custom-client”, then you declare it in ten configurations.

Here are some properties for example, for the exhaustive list of properties look into the spring documentation : https://docs.spring.io/spring-security/reference/servlet/oauth2/login/core.html.

spring:
  application:
    name: sample-spring-boot-oidc
  security:
    oauth2:
      client:
        registration:
          custom-client:
            authorization-grant-type: client_credentials
            client-authentication-method: client_secret_post
            client-id: [Client-ID]
            client-secret: [Secret]
            provider: custom-client
        provider:
          custom-client:
            token-uri: https://[IDP Host]/[....]/token

Second you have to build a token provider that will get your registered OIDC provider from the Registration Bean.

import static java.util.Objects.isNull;
import java.util.Optional;
import org.springframework.lang.Nullable;
import org.springframework.security.oauth2.client.AuthorizedClientServiceReactiveOAuth2AuthorizedClientManager;
import org.springframework.security.oauth2.client.InMemoryReactiveOAuth2AuthorizedClientService;
import org.springframework.security.oauth2.client.OAuth2AuthorizeRequest;
import org.springframework.security.oauth2.client.OAuth2AuthorizedClient;
import org.springframework.security.oauth2.client.ReactiveOAuth2AuthorizedClientManager;
import org.springframework.security.oauth2.client.ReactiveOAuth2AuthorizedClientProvider;
import org.springframework.security.oauth2.client.ReactiveOAuth2AuthorizedClientProviderBuilder;
import org.springframework.security.oauth2.client.ReactiveOAuth2AuthorizedClientService;
import org.springframework.security.oauth2.client.registration.ClientRegistration;
import org.springframework.security.oauth2.client.registration.InMemoryReactiveClientRegistrationRepository;
import org.springframework.security.oauth2.client.registration.ReactiveClientRegistrationRepository;
import org.springframework.security.oauth2.core.OAuth2AccessToken;
import org.springframework.security.oauth2.core.OAuth2Token;

public class Oauth2TokenSupplierImpl implements TokenSupplier {

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

  // `ClientRegistration` is an autoconfigured @Bean provided by Spring, that mean YOU have to get it from the context during the startup sequence and inject it into this constructor. 
  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) {
    // Since we connect with `client-id` and `client-secret`, using a `principal` is useless.
    OAuth2AuthorizeRequest req = OAuth2AuthorizeRequest.withClientRegistrationId(registration.getRegistrationId()).principal("N/A").build();
    OAuth2AuthorizedClient client = Optional.of(manager.authorize(req).block()).get();
    return client.getAccessToken();
  }
}

Third you’re ready to get a token supplier instance, using the ClientRegistrationRepository and the provider you registered.

public class TokenSupplierFactory {

  TokenSupplier tokenSupplierBuilder(String providerName, ClientRegistrationRepository clientRegistrationRepository) {
    return new Oauth2TokenSupplierImpl(clientRegistrationRepository.findByRegistrationId(providerName));
  }
}

B.N.: Here are the TokenSupplier interface ans TokenUtils class

import java.util.function.Supplier;
import org.springframework.security.oauth2.core.OAuth2Token;

public interface TokenSupplier extends Supplier<OAuth2Token> {}
import java.time.Instant;
import org.springframework.security.oauth2.core.OAuth2Token;

public class TokenUtils {

  private TokenUtils() {}

  public static boolean isValid(OAuth2Token oauth2token) {
    return Instant.now().isBefore(oauth2token.getExpiresAt());
  }
}

Posted

in

by

Comments

One response to “Spring-Boot 3 – register an OIDC token provider”

Leave a Reply

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