Search code examples
spring-bootspring-securityspring-security-oauth2spring-webfluxspring-webclient

How to set the access token once during the instanciation of the webClient in spring webflux?


I try to use WebClient with oauth2 in spring webflux. I fetch a token from an url access token and i set it into the webclient. but i do not like to fetch this access token in every call of other secured endpoints. Means that i want to fetch it only in the first time during the instanciation of the webclient and when the access token expire.

Here is the code that i am using :

@Configuration
public class OauthEmployeConfig{

    /**
    ** ... String baseUrl, String accessUrl for the access token url
    **/

    @Bean
    public WebClient webClient(UserRegistration userRegistr) {

        ClientRequest clientRequest = ClientRequest
            .create(HttpMethod.POST, URI.create(accessUrl))
            .header(HttpHeaders.ACCEPT, MediaType.APPLICATION_JSON_VALUE)
            .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_FORM_URLENCODED_VALUE)
            .headers(headers -> headers.setBasicAuth(userRegistr.getClientId(), userRegistr.getClientSecret()))
            .body(BodyInserters.fromFormData("grant_type", userRegistr.getAuthorizGrantType())
                .with("scope", userRegistr.getScope().replaceAll(",", "")))
            .build();

        return WebClient.builder()
            .baseUrl(baseUrl)
            .defaultHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
            .defaultHeader(HttpHeaders.ACCEPT, MediaType.APPLICATION_JSON_VALUE)
            .filter((request, next) -> next.exchange(clientRequest)
                .flatMap(response -> response.body(org.springframework.security.oauth2.core.web.reactive.function.OAuth2BodyExtractors.oauth2AccessTokenResponse()))
                .map(accessToken -> accessToken.getAccessToken().getTokenValue())
                .map(token -> setBearer(request, token))
                .flatMap(next::exchange))
            .filter(logRequest())
            .filter(handleResponseError())
            .build();
    }

    private ClientRequest setBearer(ClientRequest request, String token) {
    return ClientRequest.from(request)
        .header("Authorization", "Bearer " + token).build();
    }


    private static ExchangeFilterFunction handleResponseError() {
        return ExchangeFilterFunction.ofResponseProcessor(
            response -> response.statusCode().isError()
                ? response.bodyToMono(String.class)
                    .flatMap(errorBody -> Mono.error(new RuntimeException(errorBody, response.statusCode())))
                : Mono.just(response));
    }

     private static ExchangeFilterFunction logRequest() {
        return ExchangeFilterFunction.ofRequestProcessor(clientRequest -> {
          clientRequest.headers().forEach((name, values) -> values.forEach(value -> LOG.info("{}={}", name, value)));
          return Mono.just(clientRequest);
        });
    }
}

Solution

  • I followed this toturial and also spring doc, and i had to change my code.

    So my code looks like :

    application.properties

    spring.security.oauth2.client.registration.chris.authorization-grant-type=client_credentials
    spring.security.oauth2.client.registration.chris.client-id=chris-client-id
    spring.security.oauth2.client.registration.chris.client-secret=chris-secret
    
    spring.security.oauth2.client.provider.chris.token-uri=http://localhost:8085/oauth/token
    

    The Configuration class:

    @Configuration
       public OauthEmployeConfig {
    
        @Bean
        WebClient webClient(ReactiveClientRegistrationRepository clientRegistrations) {
         ServerOAuth2AuthorizedClientExchangeFilterFunction oauth =
          new ServerOAuth2AuthorizedClientExchangeFilterFunction(
           clientRegistrations,
           new UnAuthenticatedServerOAuth2AuthorizedClientRepository());
         oauth.setDefaultClientRegistrationId("chris");
         oauth.setDefaultOAuth2AuthorizedClient(true);
         return WebClient.builder()
          .filter(oauth)
          .filter(logRequest())
          .filter(handleResponseError())
          .build();
        }
    
        private static ExchangeFilterFunction handleResponseError() {
         return ExchangeFilterFunction.ofResponseProcessor(
          response -> response.statusCode().isError() ?
          response.bodyToMono(String.class)
          .flatMap(errorBody -> Mono.error(new RunTimeException(errorBody, response.statusCode()))) :
          Mono.just(response));
        }
    
        private static ExchangeFilterFunction logRequest() {
         return ExchangeFilterFunction.ofRequestProcessor(clientRequest -> {
             // To log the headers details like Token ...
          clientRequest.headers().forEach((name, values) -> values.forEach(value -> LOGGER.info("{}={}", name, value)));
          return Mono.just(clientRequest);
         });
        }
       }
    

    Rest Call via webClient:

    ...
    webClient.get()
      .uri("http://localhost:8084/retrieve-resource")
      .retrieve()
    ...
    

    This approach will of course update the access token after it expiration via the refresh token.