Skip to content

HeaderWriterFilter headers not written on RequestDispatcher.forward() under Tomcat 11 #19457

Description

@mcollovati

Describe the bug
Spring Security's default response headers (Cache-Control, X-Content-Type-Options, X-XSS-Protection, X-Frame-Options, …) are not written on responses produced through a servlet RequestDispatcher.forward() when running on Tomcat 11. Hitting the same servlet directly returns all headers; only forwarded responses lose them, silently.

The trigger is Tomcat 11's changed default for the Context attribute suspendWrappedResponseAfterForward (false in Tomcat 10.1.x → true in Tomcat 11): after a forward, Catalina suspends/unwraps the wrapped response instead of closing it, so HeaderWriterFilter's OnCommittedResponseWrapper never receives its on-commit callback. The behavior tracks the Tomcat major version and is independent of the Spring Boot and Spring Security versions.

Setting HeaderWriterFilter.setShouldWriteHeadersEagerly(true) (or restoring suspendWrappedResponseAfterForward=false) works around it. Raising it here since HeaderWriterFilter is the component that silently omits the headers: should its default be made robust to Tomcat 11's behavior, or at least documented?

To Reproduce
The sample is a Spring Boot app with Spring Security (permitAll) and a servlet (TargetServlet) mapped at /target/*. It exposes the same servlet three ways (Application.java): directly (/target/*), via a URL forward request.getRequestDispatcher(...).forward(...) (/forward-url), and via a named-dispatcher forward using ServletForwardingController (/forward-named).

mvn clean package
java -jar target/ss-forward-mre-1.0.jar

curl -sD - -o /dev/null http://localhost:8090/target/direct    # headers PRESENT
curl -sD - -o /dev/null http://localhost:8090/forward-url      # headers MISSING
curl -sD - -o /dev/null http://localhost:8090/forward-named    # headers MISSING

Rebuilding against Tomcat 10.1.x (mvn clean package -Dtomcat.version=10.1.39) makes the forwarded responses keep the headers.
Likewise, on Tomcat 11 setting suspendWrappedResponseAfterForward=false restores them; run with --tomcat.suspend-fix=true (the sample wires this flag through a WebServerFactoryCustomizer).

Expected behavior
Responses produced via forward() carry the same Spring Security response headers as direct responses (the Tomcat 10.1 / Spring Boot 3.x behavior).

Sample

https://github.com/mcollovati/spring-security-forward-headers-tomcat11

Environment: Spring Boot 4.1.0, Spring Security 7.1.0, Tomcat 11.0.22, Java 21.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions