0

In my project I have a requirement where I need to call a third party api authentic url to get the the access token. I need to set that access token in every subsequent request header .The access token has some lifetime and when the lifetime expired I need to regenerate the access token.

application.yml I have hardcoded the client_id,client_secret,auth_url and grant_type .

AuthController.java here I have created an endpoint to generate the access token.

**`AuthService.java`**
@Services
@Slf4j
public class AuthService{
 @Autowired
 private WebClient webClient;
static String accessToken="";
public Mono<SeekResponse> getAccessToken(AuthRequest authRequest) throws InvalidTokenException{
  Mono<AuthResponse> authResponse=webClient.post()
                  .bodyValue(authRequest)
                  .accept(MediaType.APPLICATION_JSON)
                  .retrive()
                  .bodyToMono(AuthResponse.class);

 authResponse.doOnNext(response->{
       String value=response.getAccess_token();
       accessToken=accessToken+value; 
  })
}
}

Although I have updated the "accessToken" value but it will return me null. I understand as I have made async call this value coming as null. I can't use blocking mechanism here. Is there any other way to generate the access token and pass it as a header for the subsequent request for authentication. Or how can I use the accessToken value globally so that I can set those token value to my subsequent api request call.

I have tried with oAuth2 by following the below article: https://medium.com/@asce4s/oauth2-with-spring-webclient-761d16f89cdd But when I execute I am getting the below error : "An expected CSRF token cannot found".

stumbler
  • 647
  • 3
  • 11
  • 26

2 Answers2

0

It's hard to understand provided sample and implementation is not really reactive. The method returns Mono but at the same time throws InvalidTokenException or usage of onNext that is a so-called side-effect operation that should be used for logging, metrics, or other similar use cases.

The way you implement oauth flow for WebClient is to create filter, Client Filters.

Spring Security provides some boilerplates for common oauth flows. Check Spring Security OAuth2 for more details.

Here is an example of simple implementation of the client credential provider

private ServerOAuth2AuthorizedClientExchangeFilterFunction oauth(String clientRegistrationId, ClientConfig config) {
    var clientRegistration = ClientRegistration
            .withRegistrationId(clientRegistrationId)
            .tokenUri(config.getAuthUrl() + "/token")
            .clientId(config.getClientId())
            .clientSecret(config.getClientSecret())
            .authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS)
            .build();

    var authRepository = new InMemoryReactiveClientRegistrationRepository(clientRegistration);
    var authClientService = new InMemoryReactiveOAuth2AuthorizedClientService(authRepository);

    var authClientManager = new AuthorizedClientServiceReactiveOAuth2AuthorizedClientManager(
            authRepository, authClientService);

    var oauth = new ServerOAuth2AuthorizedClientExchangeFilterFunction(authClientManager);
    oauth.setDefaultClientRegistrationId(clientRegistrationId);
    return oauth;
}

then you could use it in the WebClient

WebClient.builder()
    .filter(oauth)
    .build()

UPDATE Here is an example of the alternative method without filters

AuthService

@Service
public class AuthService {
    private final WebClient webClient;

    public AuthService() {
        this.webClient = WebClient.create("<url>/token");
    }

    public Mono<String> getAccessToken() {
        return webClient.post()
                .bodyValue()
                .accept(MediaType.APPLICATION_JSON)
                .retrieve()
                .bodyToMono(AuthResponse.class)
                .map(res -> res.getAccessToken());
    }
}

ApiService

@Service
public class ApiService {
    private final WebClient webClient;
    private final Mono<String> requestToken;

    public ApiService(AuthService authService) {
        this.webClient = WebClient.create("<url>/api");
        // cache for token expiration
        this.requestToken = authService.getAccessToken().cache(Duration.ofMinutes(10));
    }

    public Mono<String> request() {
        return requestToken
                .flatMap(token ->
                        webClient.get()
                                .header(HttpHeaders.AUTHORIZATION, "Bearer " + token)
                                .accept(MediaType.APPLICATION_JSON)
                                .retrieve()
                                .bodyToMono(String.class)
                );
    }
}
Alex
  • 4,987
  • 1
  • 8
  • 26
  • If we are not doing any oAuth2.0 approach instead calling the the 3rd party authetication url and getting the access token and use it for subsequent request. Is it a good approach? – stumbler Mar 07 '22 at 11:09
  • I want to store the access token as global variable and then will reuse that for subsequent request. But with the reactive approach it is not allowing me. – stumbler Mar 07 '22 at 13:32
  • I would still recommend using filter but as an alternative approach you could store token `Mono` in the class field like `Mono accessToken = getAccessToken().cache()` and then use it in the request `accessToken.flatMap(token -> request(token))`. This way accessToken `Mono` would be resolved only once (assuming token has no expiration). You could also cache it for a limited time using `cache(Duration.ofMinutes(10))` to update token every X minutes. – Alex Mar 07 '22 at 17:43
  • When other request needs that access token how that can be accessed/ – stumbler Mar 07 '22 at 18:32
  • Can you please share some reference ?Suppose in AuthService.java , I have cached the access token ,Mono accessToken=authResponse.map(token->token.getAccess_token)).cache(Duration.ofMinutes(10));. Now, I have different service class in which I need to fetch userDetails by setting the token value in header.In this situation how will my other service class retrieve the value from cache. – stumbler Mar 07 '22 at 18:40
  • does this line means I need to create a method which contains token as parameter.accessToken.flatMap(token -> request(token)) – stumbler Mar 07 '22 at 18:42
  • I would rather inject AuthService into the class that makes a request and initialize `Mono accessToken = getAccessToken().cache()` in the constructor. You could also encapsulate this in the AuthService and return cached `Mono`. But the key here is that `getAccessToken()` is async and you need to construct reactive flow – Alex Mar 07 '22 at 20:10
  • Thing is that from Mono I need to extract the token and set it into cache as a string. Within Mono it is fine I can extract it as a String but outside of Mono token is coming as null. – stumbler Mar 08 '22 at 01:36
  • @stumbler updated the answer and added one more example based on comments – Alex Mar 11 '22 at 01:37
  • I can now access the token as a static variable . In service class where I need to set the token as header I have checked if token has empty or not .If empty I am calling my AuthService to generate the token and then save it in a static variable. authService.getAccessToken(authRequest).flatMap(response->accessToken=response.getAccessToken(); – stumbler Mar 11 '22 at 11:05
  • But now I want to set in Cache with expiry time. – stumbler Mar 11 '22 at 11:06
0

I'm also learning Webflux. Here's my thought. Please correct me if I'm wrong.

We are not going to rely on doOnNext() nor doOnSuccess() nor other similar method to try to work on an pre-defined variable accessToken (That's not a way to let Mono flow). What we should focus on is converting a mono to another mono, for example converting mono response to mono access token.

The way to do that is .flatmap()/.map()/.zipwith()/... For example,

Mono<string> tokenMono = responseMono.flatmap(
    // in the map or flatmap, we get the chance to operate on  variables/objects.
    resp -> { 
        string token = response.getAccess_token();
        return Mono.just(token); // with Mono.just(), we are able to convert object to Mono again.
    }
) // this example is not practical, as map() is better to do the same thing. flatmap with Mono.just() is meaningless here.

Mono<string> tokenMono2 = responseMono.map(
        resp -> { 
            string token = response.getAccess_token();
            return token;
        }
    ) 

Everything starting from Mono should be always Mono until subscribed or blocked. And they provide us ways to operate on those variables inside Mono<variables>. Those are map() flatmap(), zipwith(), etc.

https://stackoverflow.com/a/60105107/18412317 Referring to a point this author said, doOnNext() is for side effect such as logging.

ypan
  • 24
  • 3