Skip to content

HttpHeaders.writeHttpHeaders Fails with UnsupportedOperationException #15989

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
spencergibb opened this issue Oct 24, 2024 · 3 comments
Closed
Assignees
Labels
in: web An issue in web modules (web, webmvc) status: invalid An issue that we don't feel is valid type: bug A general bug

Comments

@spencergibb
Copy link
Member

spencergibb commented Oct 24, 2024

Superseded by spring-projects/spring-framework#33789

Related spring-cloud/spring-cloud-gateway#3568 #15995 #16002 #16013

Workaround #15989 (comment)

Updated Description

When using WebFlux + Spring Cloud + Spring Security'sStrictServerWebExchangeFirewall the following exception occurs

java.lang.UnsupportedOperationException at org.springframework.http.ReadOnlyHttpHeaders.set(ReadOnlyHttpHeaders.java:110)
Suppressed: The stacktrace has been enhanced by Reactor, refer to additional information below: 
Error has been observed at the following site(s):
*__checkpoint ⇢ org.springframework.cloud.gateway.filter.WeightCalculatorWebFilter [DefaultWebFilterChain]
*__checkpoint ⇢ AuthorizationWebFilter [DefaultWebFilterChain]
*__checkpoint ⇢ ExceptionTranslationWebFilter [DefaultWebFilterChain]
*__checkpoint ⇢ LogoutWebFilter [DefaultWebFilterChain]
*__checkpoint ⇢ ServerRequestCacheWebFilter [DefaultWebFilterChain]
*__checkpoint ⇢ SecurityContextServerWebExchangeWebFilter [DefaultWebFilterChain]
*__checkpoint ⇢ LogoutPageGeneratingWebFilter [DefaultWebFilterChain]
*__checkpoint ⇢ LoginPageGeneratingWebFilter [DefaultWebFilterChain]
*__checkpoint ⇢ OAuth2AuthorizationCodeGrantWebFilter [DefaultWebFilterChain]
*__checkpoint ⇢ AuthenticationWebFilter [DefaultWebFilterChain]
*__checkpoint ⇢ OAuth2LoginAuthenticationWebFilter [DefaultWebFilterChain]
*__checkpoint ⇢ OAuth2AuthorizationRequestRedirectWebFilter [DefaultWebFilterChain]
*__checkpoint ⇢ OAuth2AuthorizationRequestRedirectWebFilter [DefaultWebFilterChain]
*__checkpoint ⇢ ReactorContextWebFilter [DefaultWebFilterChain]
*__checkpoint ⇢ CsrfWebFilter [DefaultWebFilterChain]
*__checkpoint ⇢ HttpsRedirectWebFilter [DefaultWebFilterChain]
*__checkpoint ⇢ HttpHeaderWriterWebFilter [DefaultWebFilterChain]
*__checkpoint ⇢ ServerWebExchangeReactorContextWebFilter [DefaultWebFilterChain]
*__checkpoint ⇢ org.springframework.security.web.server.WebFilterChainProxy [DefaultWebFilterChain]
*__checkpoint ⇢ org.springframework.security.web.server.WebFilterChainProxy [DefaultWebFilterChain]
*__checkpoint ⇢ HTTP GET \"/api/foo\" [ExceptionHandlingWebHandler]\nOriginal Stack Trace:
    at org.springframework.http.ReadOnlyHttpHeaders.set(ReadOnlyHttpHeaders.java:110)
    at org.springframework.http.ReadOnlyHttpHeaders.set(ReadOnlyHttpHeaders.java:39)
    at org.springframework.http.HttpHeaders.set(HttpHeaders.java:1735)
    at org.springframework.http.HttpHeaders.set(HttpHeaders.java:76)
    at org.springframework.http.HttpHeaders.set(HttpHeaders.java:1735)
    at org.springframework.http.HttpHeaders.setBearerAuth(HttpHeaders.java:830)
    at org.springframework.cloud.gateway.filter.factory.TokenRelayGatewayFilterFactory.lambda$withBearerAuth$5(TokenRelayGatewayFilterFactory.java:92)
    at org.springframework.http.server.reactive.DefaultServerHttpRequestBuilder.headers(DefaultServerHttpRequestBuilder.java:117)
    at org.springframework.cloud.gateway.filter.factory.TokenRelayGatewayFilterFactory.lambda$withBearerAuth$6(TokenRelayGatewayFilterFactory.java:92)
    at org.springframework.web.server.DefaultServerWebExchangeBuilder.request(DefaultServerWebExchangeBuilder.java:58)
    at org.springframework.cloud.gateway.filter.factory.TokenRelayGatewayFilterFactory.withBearerAuth(TokenRelayGatewayFilterFactory.java:92)
    at org.springframework.cloud.gateway.filter.factory.TokenRelayGatewayFilterFactory.lambda$apply$2(TokenRelayGatewayFilterFactory.java:65)
    

Original Description

Rob and I chatted about it

https://github.com/spring-projects/spring-framework/blob/c27a5687dcc8708584edd0141630af66ce6cbe90/spring-web/src/main/java/org/springframework/http/HttpHeaders.java#L1890

headers is read only, AND headers.headers is read only, so calling HttpHeaders.writableHttpHeaders() does not, in fact, result in writable headers.

The question is, is it a security or framework bug.

/cc @rwinch @rstoyanchev

@spencergibb spencergibb added status: waiting-for-triage An issue we've not yet triaged type: bug A general bug labels Oct 24, 2024
@rwinch rwinch self-assigned this Oct 24, 2024
@rwinch rwinch added status: invalid An issue that we don't feel is valid and removed status: waiting-for-triage An issue we've not yet triaged labels Oct 24, 2024
@rwinch
Copy link
Member

rwinch commented Oct 24, 2024

I'm going to close this issue in favor of spring-projects/spring-framework#33789

@rwinch rwinch closed this as completed Oct 24, 2024
@rwinch rwinch changed the title The StrictFirewallServerWebExchange causes the ServerWebExchange.getRequest().mutate() contract to break as headers become readonly HttpHeaders.writeHttpHeaders Fails with UnsupportedOperationException Oct 28, 2024
@rwinch
Copy link
Member

rwinch commented Oct 28, 2024

For those experiencing this issue, DO NOT disable Spring Security's firewall as a workaround to the underlying Spring Framework issue. Instead, you can use the following as a workaround:

@Bean
@Order(Ordered.HIGHEST_PRECEDENCE)
WebFilter writeableHeaders() {
	return (exchange, chain) -> {
		HttpHeaders writeableHeaders = HttpHeaders.writableHttpHeaders(
				exchange.getRequest().getHeaders());
		ServerHttpRequestDecorator writeableRequest = new ServerHttpRequestDecorator(
				exchange.getRequest()) {
			@Override
			public HttpHeaders getHeaders() {
				return writeableHeaders;
			}
		};
		ServerWebExchange writeableExchange = exchange.mutate()
			.request(writeableRequest)
			.build();
		return chain.filter(writeableExchange);
	};
}

You can find a complete demo of the workaround at https://github.com/rwinch/spring-sample/tree/spring-framework-33789-readonly-headers

@dongKos
Copy link

dongKos commented Oct 31, 2024

@rwinch
your solution works like magic thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
in: web An issue in web modules (web, webmvc) status: invalid An issue that we don't feel is valid type: bug A general bug
Projects
None yet
Development

No branches or pull requests

3 participants