Describe the bug
Since spring-security 6.3.4 headers from firewalled requests from the new StrictServerWebExchangeFirewall, which was added here, cannot be mutated.
The reason is:
A call to mutate() on the exchange uses the DefaultServerHttpRequestBuilder which initializes the headers with a call to HttpHeaders.writableHttpHeaders(original.getHeaders()).
And in HttpHeaders.writableHttpHeaders new HttpHeaders are created if there are ReadOnlyHttpHeaders.
But the StrictServerWebExchangeFirewall wraps the original headers with the delegate class StrictFirewallHttpHeaders. Thus, those headers are not ReadOnlyHttpHeaders anymore.
To Reproduce See sample below.
Expected behavior
The headers can be mutated, after calling mutate() even for firewalled requests.
Sample
Testcase:
package com.exmple.securityreadonlyheaders;
import org.junit.jupiter.api.Test;
import org.springframework.mock.http.server.reactive.MockServerHttpRequest;
import org.springframework.mock.web.server.MockServerWebExchange;
import org.springframework.security.web.server.firewall.StrictServerWebExchangeFirewall;
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
class SecurityReadonlyHeadersApplicationTests {
@Test
void firewalledExchange() {
final var exchange = MockServerWebExchange.from(MockServerHttpRequest.get("/test").build());
var firewall = new StrictServerWebExchangeFirewall();
var firewalledExchange = firewall.getFirewalledExchange(exchange).block();
assertDoesNotThrow(() -> firewalledExchange.mutate()
.request(r -> r.headers(h -> h.set("Content-Type", "application/json"))));
}
}
Comment From: rwinch
Thanks for the report @LeovR this is a duplicate of https://github.com/spring-projects/spring-security/issues/15989 ( workaround on it) which is superseded by https://github.com/spring-projects/spring-framework/issues/33789