Skip to content

Revise cookies support with Apache HTTP Components in WebClient and WebTestClient #33822

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
MFAshby opened this issue Oct 30, 2024 · 5 comments
Closed
Assignees
Labels
in: web Issues in web modules (web, webmvc, webflux, websocket) type: enhancement A general enhancement
Milestone

Comments

@MFAshby
Copy link

MFAshby commented Oct 30, 2024

Steps to reproduce

  • Setup Spring WebTestClient using apache httpcomponents as the underlying HTTP client library.
  • Send a request with some cookies
  • Read cookies from the response

Expected bevaviour:

only cookies which were returned from a 'Set-Cookie' header in the HTTP response are found when calling org.springframework.test.web.reactive.server.ExchangeResult#getResponseCookies.

Actual behaviour:

cookies set on the request are also present in getResponseCookies, with spurious 'Path' metadata, even though no such cookies were returned from the server.

It seems like the problem is in org.springframework.http.client.reactive.HttpComponentsClientHttpRequest#applyCookies: this is creating entries in the cookieStore with 'path' and 'domain' metadata set to whatever we are currently requesting.

	@Override
	protected void applyCookies() {
		if (getCookies().isEmpty()) {
			return;
		}

		CookieStore cookieStore = this.context.getCookieStore();

		getCookies().values()
				.stream()
				.flatMap(Collection::stream)
				.forEach(cookie -> {
					BasicClientCookie clientCookie = new BasicClientCookie(cookie.getName(), cookie.getValue());
					clientCookie.setDomain(getURI().getHost());
					clientCookie.setPath(getURI().getPath());
					cookieStore.addCookie(clientCookie);
				});
	}

Then this same cookieStore then used to store response cookies; see org.springframework.http.client.reactive.HttpComponentsClientHttpResponse#getCookies:

	@Override
	public MultiValueMap<String, ResponseCookie> getCookies() {
		LinkedMultiValueMap<String, ResponseCookie> result = new LinkedMultiValueMap<>();
		this.context.getCookieStore().getCookies().forEach(cookie ->
				result.add(cookie.getName(),
						ResponseCookie.fromClientResponse(cookie.getName(), cookie.getValue())
								.domain(cookie.getDomain())
								.path(cookie.getPath())
								.maxAge(getMaxAgeSeconds(cookie))
								.secure(cookie.isSecure())
								.httpOnly(cookie.containsAttribute("httponly"))
								.sameSite(cookie.getAttribute("samesite"))
								.build()));
		return result;
	}

I can prepare a simple example project shortly.

@spring-projects-issues spring-projects-issues added the status: waiting-for-triage An issue we've not yet triaged or decided on label Oct 30, 2024
@MFAshby
Copy link
Author

MFAshby commented Oct 30, 2024

Furthermore the WebTestClient API WebTestClient.RequestHeadersSpec#cookies doesn't expose the cookies that are supplied from apache httpcomponents default cookie store, contrary to what the documentation comment suggests. This prevents the user from having full control over cookies:

		/**
		 * Manipulate this request's cookies with the given consumer. The
		 * map provided to the consumer is "live", so that the consumer can be used to
		 * {@linkplain MultiValueMap#set(Object, Object) overwrite} existing header values,
		 * {@linkplain MultiValueMap#remove(Object) remove} values, or use any of the other
		 * {@link MultiValueMap} methods.
		 * @param cookiesConsumer a function that consumes the cookies map
		 * @return this builder
		 */
		S cookies(Consumer<MultiValueMap<String, String>> cookiesConsumer);

@MFAshby MFAshby changed the title Incorrect cookies returned from ExchangeResult#getResponseCookies when using Apache HTTP Components Incorrect cookies returned from ExchangeResult#getResponseCookies when using Apache HTTP Components with WebTestClient Oct 30, 2024
@rstoyanchev
Copy link
Contributor

You raise a good point that is more general to our Apache HttpClient support for WebClient an WebTestClient.

For client side cookies we only aim to facilitate setting the request, and reading the response, but not to store them especially across requests or paths and domains. In that sense, I'm not sure that our use of the CookieStore is a good fit, and can lead to surprises and side effects.

HttpComponentsClientConnector obtains an HttpClientContext with the CookieStore per request in . If that is a new instance per request, then request cookies work as expected, but getting the response cookies from the store is surprising if the expectation is the actual SET_COOKIE header on the response. If HttpClientContext (and the CookieStore it holds) is re-used across requests, then the store contains cookies for different paths and domains, and having its full content returned from the client response is plainly wrong.

For comparison, we recently added support for cookies to RestClient #33697 where simply serialize cookies to request headers. We can adjust to do something similar in the reactive package, which would be a breaking change.

@rstoyanchev rstoyanchev added in: web Issues in web modules (web, webmvc, webflux, websocket) status: waiting-for-internal-feedback An issue that needs input from a member or another Spring Team labels Nov 1, 2024
@rstoyanchev rstoyanchev self-assigned this Nov 5, 2024
@rstoyanchev rstoyanchev added type: enhancement A general enhancement and removed status: waiting-for-triage An issue we've not yet triaged or decided on status: waiting-for-internal-feedback An issue that needs input from a member or another Spring Team labels Nov 5, 2024
@rstoyanchev rstoyanchev added this to the 6.2.0 milestone Nov 5, 2024
@rstoyanchev rstoyanchev changed the title Incorrect cookies returned from ExchangeResult#getResponseCookies when using Apache HTTP Components with WebTestClient Incorrect cookies returned from ExchangeResult#getResponseCookies with Apache HTTP Components Nov 5, 2024
@rstoyanchev rstoyanchev changed the title Incorrect cookies returned from ExchangeResult#getResponseCookies with Apache HTTP Components Revise cookies support with Apache HTTP Components in WebClient and WebTestClient Nov 7, 2024
@MFAshby
Copy link
Author

MFAshby commented Nov 11, 2024

Thanks @rstoyanchev ! This is great stuff.

I'm not sure that our use of the CookieStore is a good fit, and can lead to surprises and side effects.

I'd note that without the user explicitly disabling cookie management in the underlying HttpClient (see HttpClientBuilder#disableCookieManagement) that the cookiestore is still used by default and therefore cookies received in previous requests may be sent in a new one without explicitly adding them. Should WebClient enforce disabling cookie management, or log a warning if cookie management is enabled?

I left a couple of comments on the implementation also;

37243f4#r148896291

37243f4#r148896413

@rstoyanchev
Copy link
Contributor

Re-opening to address the feedback.

@rstoyanchev rstoyanchev reopened this Nov 12, 2024
@rstoyanchev
Copy link
Contributor

rstoyanchev commented Nov 13, 2024

About the CookieStore, I don't think we need to get in the way of someone wanting to use that vs passing them on every request. I see, however, our HttpComponentsClientHttpConnector sets up a BasicCookieStore by default, and that should not longer be necessary. I will remove it.

I also responded on the commit comments.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
in: web Issues in web modules (web, webmvc, webflux, websocket) type: enhancement A general enhancement
Projects
None yet
Development

No branches or pull requests

3 participants