Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Cookie Management For Webflux WebClient

I have a WebClient that sends a JSON object with login credentials to a remote server. The remote server then returns the cookie. After which I need to POST data to that remote server along with the cookie. However, I cannot work out how re-use the cookie within the POST.

As far as I can tell, the login response gives the following structure MultiValueMap<String, ResponseCookie>, however the code to set the cookie on the POST requires MultiValueMap<String, String> or just cookie(String, String).

I assume that I must be missing some converter magic, but what? Do I even need return the whole cookie?

The cookie looks like this;

{SSO_Sticky_Session-47873-loadBalancedAdminGrp=[SSO_Sticky_Session-47873-loadBalancedAdminGrp=BNAMAKAKJABP; Path=/; HttpOnly], AUTH_TOKEN=[AUTH_TOKEN=v0l3baVZejIKjdzA1KGpkz4ccnosE6rKLQig1D2bdb-voFmVrF_aaYgzWl3Yc8QK; Path=/], uid=[uid=sjzipQdBtU30OlVbPWtDK2625i24i6t6g3Rjl5y5XcI=; Path=/], __cfduid=[__cfduid=dd872f39fd1d3bfe2a5c7316cd9ff63cd1554623603; Path=/; Domain=.aDomain.net; Max-Age=31535999; Expires=Mon, 6 Apr 2020 07:53:23 GMT; HttpOnly], JSESSIONID=[JSESSIONID=A264A713AD060EE12DA8215AEF66A3C0; Path=/aPath/; HttpOnly]}

My code is below. I have removed content type for brevity;

WebClient webClient = WebClient.create("https://remoteServer");
MultiValueMap<String, ResponseCookie> myCookies;

webClient
  .post()
  .uri("uri/login")
  .body(Mono.just(myLoginObject), MyLogin.class)
  .exchange()
  .subscribe(r -> 
    System.err.println("Received:" + r.cookies());
    myCookies = r.cookies();
   );

webClient
  .post()
  .uri("/uri/data")
  .cookies(????) // what goes here ??
  .body(....)
  .exchange();
like image 706
lafual Avatar asked Apr 07 '19 09:04

lafual


2 Answers

Since .exchange() has been deprecated but this thread comes up on popular search machines, let me add a code example using .exchangeToMono() below for future references.

Please note that I use an ExchangeFilterFunction which will send the authorization request before each request sent by the webClient bean:

@Bean("webClient")
public WebClient webClient(ReactorResourceFactory resourceFactory,
    ExchangeFilterFunction authFilter) {
    var httpClient = HttpClient.create(resourceFactory.getConnectionProvider());
    var clientHttpConnector = new ReactorClientHttpConnector(httpClient);
    return WebClient.builder().filter(authFilter).clientConnector(clientHttpConnector)
        .build();
}

@Bean("authWebClient")
public WebClient authWebClient(ReactorResourceFactory resourceFactory) {
    var httpClient = HttpClient.create(resourceFactory.getConnectionProvider());
    var clientHttpConnector = new ReactorClientHttpConnector(httpClient);
    return WebClient.builder().clientConnector(clientHttpConnector).build();
}

@Bean
public ExchangeFilterFunction authFilter(@Qualifier("authWebClient") WebClient authWebClient,
    @Value("${application.url:''}") String url,
    @Value("${application.basic-auth-credentials:''}") String basicAuthCredentials) {
return (request, next) -> authWebClient.get()
    .uri(url)
    .header("Authorization", String.format("Basic %s", basicAuthCredentials))
    .exchangeToMono(response -> next.exchange(ClientRequest.from(request)
        .headers(headers -> {
            headers.add("Authorization", String.format("Basic %s", basicAuthCredentials));
        })
        .cookies(readCookies(response))
        .build()));
}

private Consumer<MultiValueMap<String, String>> readCookies(ClientResponse response) {
return cookies -> response.cookies().forEach((responseCookieName, responseCookies) ->
    cookies.addAll(responseCookieName,
        responseCookies.stream().map(responseCookie -> responseCookie.getValue())
            .collect(Collectors.toList())));
}
like image 110
J. S. Avatar answered Oct 26 '22 01:10

J. S.


Having written server side Java and JSP for a number of years, I had largely ignored the concept of cookies, as management is taken care of by (for example) Tomcat on the server side and by the browser on the client side. Any search for cookie handling in Spring always focused on the Spring server and rarely on Spring actually being a client of another server. Any examples for WebClient were simplistic and didn't assume any form of security negotiation.

Having read a cookie explanation Wikipedia Cookies and the cookie standard RFC6265, it made sense to me why the incoming cookie is in class ResponseCookie and the outgoing cookie was a String. The incoming cookie has additional meta-data on (for example) Domain, Path and Max-Age.

For my implementation, the vendor didn't specify which cookies needed to be returned, so I ended up returning all of them. Therefore, my amended code is as follows;

WebClient webClient = WebClient.create("https://remoteServer");
MultiValueMap<String, String> myCookies = new LinkedMultiValueMap<String, String>()

webClient
  .post()
  .uri("uri/login")
  .body(Mono.just(myLoginObject), MyLogin.class)
  .exchange()
  .subscribe(r -> 
      for (String key: r.cookies().keySet()) {
        myCookies.put(key, Arrays.asList(r.cookies().get(key).get(0).getValue()));
      }
   );

webClient
  .post()
  .uri("/uri/data")
  .cookies(cookies -> cookies.addAll(myCookies))
  .body(....)
  .exchange();

like image 36
lafual Avatar answered Oct 26 '22 02:10

lafual