2

I need to call an third party API which requires an authentication call beforehand to get an authentication token. The Authentication API is in json but the subsequent call is in XML.

I have separately :

webclient.post().uri("/auth").header(ACCEPT,JSON).retrieve()
      .bodyToMono(AuthToken.class);
webclient.post().uri("/api").header(ACCEPT,XML).header("AUTH",authToken).retrive().bodyToFlux();

How should I implement the method to be able to access the second API? I tried to assign a variable inside the method with token = firstCall.block() but I've got block() is not supported error.

LunaticJape
  • 1,446
  • 4
  • 21
  • 39

1 Answers1

3

You just have to transform the original flux like:

webclient.post().uri("/auth")
    .header(ACCEPT,JSON)
    .retrieve()
    .bodyToMono(AuthToken.class)
    .flatMapMany(authToken -> webclient.post().uri("/api")
    .header(ACCEPT,XML)
    .header("AUTH",authToken).retrive().bodyToFlux();

A better solution would be to use a ExchangeFilterFunction that will fetch the token for you https://docs.spring.io/spring-framework/docs/current/spring-framework-reference/web-reactive.html#webflux-client-filter

Something like that (not tested might have bug):

WebClient authWebClient = WebClient.builder().build();
WebClient webClient = WebClient.builder()
        .filter(((request, next) -> authWebClient.post()
                .uri("/auth")
                .accept(MediaType.APPLICATION_JSON)
                .retrieve()
                .bodyToMono(AuthToken.class)
                .flatMap(authToken -> next.exchange(ClientRequest.from(request)
                        .headers(headers -> headers.add("AUTH", authToken))
                        .build()))
        ))
        .build();

webClient.post().uri("/api")
        .accept(MediaType.APPLICATION_XML)
        .retrieve()
        .bodyToFlux(MyData.class);

This is basic but you could add cache to avoid requesting or fetch again if token is expired... Be aware that builtin ExchangeFilterFunction exists for basic oauth2...

Wrap everything with a spring configuration:

@Configuration
public class WebClientConfiguration {
    @Bean
    public WebClient authWebClient(final WebClient.Builder webClientBuilder) {
        return webClientBuilder.build();
    }

    @Bean
    public ExchangeFilterFunction authFilter(final WebClient authWebClient) {
        return (request, next) -> authWebClient.post()
                .uri("/auth")
                .accept(MediaType.APPLICATION_JSON)
                .retrieve()
                .bodyToMono(AuthToken.class)
                .flatMap(authToken -> next.exchange(ClientRequest.from(request)
                        .headers(headers -> headers.add("AUTH", authToken.toString()))
                        .build()));
    }

    @Bean
    public WebClient webClient(final WebClient.Builder webClientBuilder, final ExchangeFilterFunction authFilter) {
        return webClientBuilder
                .filter(authFilter)
                .build();
    }
}

Or if you want to avoid lambda:

@Configuration
public class WebClientConfiguration {
@Bean
    public WebClient authWebClient(final WebClient.Builder webClientBuilder) {
        return webClientBuilder.build();
    }

    @Bean
    public WebClient webClient(final WebClient.Builder webClientBuilder, final AuthFilter authFilter) {
        return webClientBuilder
                .filter(authFilter)
                .build();
    }
    
    @Bean
    public AuthFilter authFilter(WebClient authWebClient) {
        return new AuthFilter(authWebClient);
    }
}

public class AuthFilter implements ExchangeFilterFunction {

    private final WebClient authWebClient;

    public AuthFilter(WebClient authWebClient) {
        this.authWebClient = authWebClient;
    }

    @Override
    public Mono<ClientResponse> filter(final ClientRequest request, final ExchangeFunction next) {
        return authWebClient.post()
                .uri("/auth")
                .accept(MediaType.APPLICATION_JSON)
                .retrieve()
                .bodyToMono(AuthToken.class)
                .flatMap(authToken -> next.exchange(ClientRequest.from(request)
                        .headers(headers -> headers.add("AUTH", authToken.toString()))
                        .build()));
    }

}
JEY
  • 6,973
  • 1
  • 36
  • 51
  • Thanks, could please give me an example of how to fetch token through another webclient using ExchangeFilter? – LunaticJape Oct 22 '20 at 05:42
  • I add an example but don't know if it works accordingly. Keep in mind that if you are using oauth2 spring security provide the required filter that you just need to use. – JEY Oct 22 '20 at 07:54
  • How is authWebClient autowired in your example? I couldn't use @Autowire in ExchangeFilterFunction. – LunaticJape Nov 04 '20 at 19:17
  • 1
    That basic dependency injection with spring. I edited my answer to wrap everything. – JEY Nov 04 '20 at 20:01
  • Great, thanks a lot. I wonder if I can use the same webclient of the request in the filter if I have multiple webclients in my app. – LunaticJape Nov 05 '20 at 18:39