Superseded by https://github.com/spring-projects/spring-framework/issues/33789

Related https://github.com/spring-cloud/spring-cloud-gateway/issues/3568 https://github.com/spring-projects/spring-security/issues/15995 https://github.com/spring-projects/spring-security/issues/16002 #16013

Workaround https://github.com/spring-projects/spring-security/issues/15989#issuecomment-2442660753

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

Comment From: rwinch

I'm going to close this issue in favor of https://github.com/spring-projects/spring-framework/issues/33789

Comment From: rwinch

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

Comment From: dongKos

@rwinch your solution works like magic thanks!

Comment From: TimofejOv

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

Hello.

What could be the solution for WebMvc case? I get UnsupportedOperationException in app with spring-cloud-gateway-mvc (4.3.1)+ spring-security (6.5.5) + spring-webmvc (6.2.11).

Thank you.

Comment From: rodrigorodrigues

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

Hi @rwinch, can you please suggest a solution that works for Spring Boot 4? It seems this method HttpHeaders.writableHttpHeaders is being removed.

Image