2

I'm new to Spring boot and reactive programming.

I'm using spring webflux webclient for an external api service. I need to fetch the auth token and set it in the header

WebClient.builder()
            .baseUrl(baseUrl)
            .filter((request, next) -> {
                return next.exchange(request)
                        .flatMap((Function<ClientResponse, Mono<ClientResponse>>) clientResponse -> {
                            if (clientResponse.statusCode().value() == 401) {
                                return authenticate().map(token -> {
                                    Token accessToken = authenticate().block();
                                    ClientRequest retryRequest = ClientRequest.from(request).header("Authorisation", "Bearer " + accessToken.getAccessToken()).build();
                                    return next.exchange(retryRequest);
                                }).
                            } else {
                                return Mono.just(clientResponse);
                            }
                        });
            })
            .defaultHeader("Authorization", "Bearer " + authToken.getAccessToken())
            .build();


private Mono<Token> authenticate() {
    MultiValueMap<String, String> params = new LinkedMultiValueMap();
    params.add("client_id", clientId);
    params.add("client_secret", clientSecret);
    params.add("grant_type", "password");
    params.add("username", username);
    params.add("password", password);

    WebClient client = WebClient.create(baseUrl);
    return client
            .post()
            .uri(tokenUri)
            .accept(MediaType.APPLICATION_JSON)
            .contentType(MediaType.APPLICATION_FORM_URLENCODED)
            .syncBody(params)
            .retrieve()
            .bodyToMono(Token.class);
}

private static class Token {
    @JsonProperty("access_token")
    private String accessToken;

    public String getAccessToken() { return accessToken; }
}

During the application startup, I'll fetch the access token and set it in the webclient builder. I've created a filter to handle authentication failures after token expiry. But the above code throws error because I've used block() which is not supposed to be used in a reactor thread. How else can I handle it? I'm using oauth2 resource owner password grant flow. Is there is any other way to handle the flow?

kirupakaranh
  • 239
  • 5
  • 14

2 Answers2

5

Hi I had the same issue (Adding a retry all requests of WebClient) which looks like you have reused. but here flatmap is your friend, if you have a Mono<Mono<T>> you can flatten it with flatMap

builder.baseUrl("http://localhost:8080")
             //sets the header before the exchange
            .filter(((request, next) -> tokenProvider.getAccessToken()
                                .map(setBearerTokenInHeader(request))
                                .flatMap(next::exchange)))
            //do the exchange
            .filter((request, next) -> next.exchange(request)
                    .flatMap(clientResponse -> {
                        if (clientResponse.statusCode().value() == 401) {
                          //If unauthenicated try again 
                            return authenticate()
                                    .flatMap(Token::getAccessToken)
                                    .map(setBearerTokenInHeader(request))
                                    .flatMap(next::exchange);
                        } else {
                            return Mono.just(clientResponse);
                        }
                    }))
            .build();

private Function<String, ClientRequest> setBearerTokenInHeader(ClientRequest request) {
        return token -> ClientRequest.from(request).header("Bearer ", token).build();
    }
Kevin Hussey
  • 1,582
  • 1
  • 10
  • 17
  • Thank you for your response. Yes, I followed your answer :) Here you've used token.getAccessToken() which will be a mono in my case. How do I set it in header? – kirupakaranh Oct 03 '18 at 07:20
  • Can you share the signature for authenicate() and Token? You should be able to flatten them if Mono> – Kevin Hussey Oct 03 '18 at 07:21
  • Thank you! It worked. :) I followed exactly what you've done here. But doesn't it send auth token request for every request since we're not setting it as default header? – kirupakaranh Oct 03 '18 at 08:02
  • Can you share the signatures of 'authToken.getAccessToken()', you'll probably need to set this before the next.exchange() ... I've updated the example – Kevin Hussey Oct 03 '18 at 08:09
  • That's just a simple getter.. I've added the code in question – kirupakaranh Oct 03 '18 at 08:17
0

I know this is an old thread, but I could not find any other working examples for the initial question

Basically, I was not able to write a working code from the above examples... With the main task: Use WebClient instance to get protected resource by providing Bearer token. The Bearer token can be requested by a separate request.

The Mono authenticate() should work fine to get a new token.

        WebClient client2 =  WebClient.builder()
                                    .baseUrl(SERVER_URL)
                                    .filter((request, next) -> {
                                        return next.exchange(request)
                                                .flatMap( clientResponse -> {
                                                    if (clientResponse.statusCode().value() == 401) {
                                                        return authenticate().map(token -> {
                                    Token accessToken = authenticate().block();
                                    ClientRequest retryRequest = ClientRequest.from(request).header("Authorisation", "Bearer " + accessToken.getAccessToken()).build();
                                    return next.exchange(retryRequest);
                                });
                                                    } else {
                                                        return Mono.just(clientResponse);
                                                    }
                                                });
                                    })
                                    .defaultHeader("Authorization", "Bearer " + token.getAccessToken())
                                    .build();

For the above example was not able to replace the ".block()" with flatMap()

And the second example

        WebClient client3 =  WebClient.builder().baseUrl("http://localhost:8080")
                                 //sets the header before the exchange
                                .filter(((request, next) -> tokenProvider.getAccessToken()
                                                    .map(setBearerTokenInHeader(request))
                                                    .flatMap(next::exchange)))
                
                                //do the exchange
                                .filter((request, next) -> next.exchange(request)
                                        .flatMap(clientResponse -> {
                                            if (clientResponse.statusCode().value() == 401) {
                                              //If unauthenicated try again 
                                                return authenticate()
                                                        .flatMap(Token::getAccessToken)
                                                        .map(setBearerTokenInHeader(request))
                                                        .flatMap(next::exchange);
                                            } else {
                                                return Mono.just(clientResponse);
                                            }
                                        }))
                                .build();

Not sure what is the "tokenProvider.getAccessToken()" and ".flatMap(Token::getAccessToken)" won't accept Due to

    class Token {
        String token = "";
        
        public String getAccessToken() { return token; }
    }

Sorry I'm new to this. If you had a working example please share in this thread

Jiga
  • 3
  • 2