Issue
I have created a simple Filter class that adds some response headers to all the resources requested. I have created a jar file and added the same to tomcat lib folder. Also the filter is added to the web.xml for url mapping /*
When I request the page of my application I can see filter is been invoked as the sysout statements are printed correctly but the headers are getting added to some of the assets only. I am pretty confused as to why it is getting added to only some assets and not to all.
Filter class as below
package com.headers.config;
import java.io.IOException;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class MySecurityHeadersFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
chain.doFilter(request, response);
HttpServletRequest httpReq = (HttpServletRequest) request;
HttpServletResponse httpResp = (HttpServletResponse) response;
httpResp.addHeader("Content-Security-Policy", "default-src 'none'; script-src 'self'; connect-src 'self'; img-src 'self'; style-src 'self';base-uri 'self';form-action 'self';");
httpResp.addHeader("Expect-CT", "max-age=86400, enforce, report-uri=https://"+ httpReq.getHeader("host").trim() +"/bham/user/reportCT");
httpResp.addHeader("Feature-Policy", "vibrate 'none'; geolocation 'none';");
httpResp.addHeader("Referrer-Policy", "no-referrer-when-downgrade");
System.out.println("Response Headers: "+((HttpServletResponse) response).getHeaderNames());
}
@Override
public void destroy() {
}
}
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.headers.config</groupId>
<artifactId>MySecurityHeadersFilter</artifactId>
<packaging>jar</packaging>
<version>1.0-SNAPSHOT</version>
<name>MySecurityHeadersFilter Maven Webapp</name>
<url>http://maven.apache.org</url>
<dependencies>
<!-- https://mvnrepository.com/artifact/javax.servlet/servlet-api -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>4.0.1</version>
<scope>provided</scope>
</dependency>
</dependencies>
<build>
<finalName>MySecurityHeadersFilter</finalName>
<sourceDirectory>src</sourceDirectory>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.1</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
</plugins>
</build>
</project>
Any help will be greatly appreciated.
I am using tomcat 9 and have added below filter mapping in web.xml. This is the only filter added
<filter>
<filter-name>MySecurityHeadersFilter</filter-name>
<filter-class>com.cdp.headers.config.MySecurityHeadersFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>MySecurityHeadersFilter</filter-name>
<url-pattern>/</url-pattern>
</filter-mapping>
<filter-mapping>
<filter-name>MySecurityHeadersFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<filter>
<filter-name>httpHeaderSecurity</filter-name>
<filter-class>org.apache.catalina.filters.HttpHeaderSecurityFilter</filter-class>
<async-supported>true</async-supported>
</filter>
<filter-mapping>
<filter-name>httpHeaderSecurity</filter-name>
<url-pattern>/</url-pattern>
</filter-mapping>
<filter-mapping>
<filter-name>httpHeaderSecurity</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<filter>
<filter-name>ExpiresFilter</filter-name>
<filter-class>org.apache.catalina.filters.ExpiresFilter</filter-class>
<init-param>
<param-name>ExpiresByType text/css</param-name>
<param-value>access plus 10 minutes</param-value>
</init-param>
<init-param>
<param-name>ExpiresByType application/javascript</param-name>
<param-value>access plus 10 minutes</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>ExpiresFilter</filter-name>
<url-pattern>*.js</url-pattern>
</filter-mapping>
<filter-mapping>
<filter-name>ExpiresFilter</filter-name>
<url-pattern>*.css</url-pattern>
</filter-mapping>
WHen I execute the URL this is what I can see in the catalina logs. The headers that I have added are not present.
Response Headers: [Strict-Transport-Security, X-Frame-Options, X-Content-Type-Options, X-XSS-Protection, Accept-Ranges, ETag, Last-Modified]
Response Headers: [Strict-Transport-Security, X-Frame-Options, X-Content-Type-Options, X-XSS-Protection, Accept-Ranges, ETag, Last-Modified]
Response Headers: [Strict-Transport-Security, X-Frame-Options, X-Content-Type-Options, X-XSS-Protection, Accept-Ranges, ETag, Last-Modified, Content-Type, Content-Length, Date, Keep-Alive, Connection, Server]
Response Headers: [Strict-Transport-Security, X-Frame-Options, X-Content-Type-Options, X-XSS-Protection]
Response Headers: [Strict-Transport-Security, X-Frame-Options, X-Content-Type-Options, X-XSS-Protection, Accept-Ranges, ETag, Last-Modified]
Response Headers: [Strict-Transport-Security, X-Frame-Options, X-Content-Type-Options, X-XSS-Protection, Accept-Ranges, ETag, Last-Modified, Cache-Control, Expires, Content-Type, Content-Length, Date, Keep-Alive, Connection, Server]
Response Headers: [Strict-Transport-Security, X-Frame-Options, X-Content-Type-Options, X-XSS-Protection, Accept-Ranges, ETag, Last-Modified, Cache-Control, Expires, Content-Type, Content-Length, Date, Keep-Alive, Connection, Server]
Response Headers: [Strict-Transport-Security, X-Frame-Options, X-Content-Type-Options, X-XSS-Protection, Accept-Ranges, ETag, Last-Modified]
Response Headers: [Strict-Transport-Security, X-Frame-Options, X-Content-Type-Options, X-XSS-Protection, Accept-Ranges, ETag, Last-Modified, Content-Type, Content-Length, Date, Keep-Alive, Connection, Server]
Response Headers: [Strict-Transport-Security, X-Frame-Options, X-Content-Type-Options, X-XSS-Protection, Accept-Ranges, ETag, Last-Modified, Content-Type, Content-Length, Date, Keep-Alive, Connection, Server]
Response Headers: [Strict-Transport-Security, X-Frame-Options, X-Content-Type-Options, X-XSS-Protection, Accept-Ranges, ETag, Last-Modified, Cache-Control, Expires, Content-Type, Content-Length, Date, Keep-Alive, Connection, Server]
Response Headers: [Strict-Transport-Security, X-Frame-Options, X-Content-Type-Options, X-XSS-Protection, Accept-Ranges, ETag, Last-Modified, Cache-Control, Expires, Content-Type, Content-Length, Date, Keep-Alive, Connection, Server]
Response Headers: [Strict-Transport-Security, X-Frame-Options, X-Content-Type-Options, X-XSS-Protection, Accept-Ranges, ETag, Last-Modified, Content-Type, Content-Length, Date, Keep-Alive, Connection, Server]
Response Headers: [Strict-Transport-Security, X-Frame-Options, X-Content-Type-Options, X-XSS-Protection, Accept-Ranges, ETag, Last-Modified, Cache-Control, Expires, Content-Type, Content-Length, Date, Keep-Alive, Connection, Server]
Response Headers: [Strict-Transport-Security, X-Frame-Options, X-Content-Type-Options, X-XSS-Protection, Accept-Ranges, ETag, Last-Modified, Cache-Control, Expires, Content-Type, Content-Length, Date, Keep-Alive, Connection, Server]
Response Headers: [Strict-Transport-Security, X-Frame-Options, X-Content-Type-Options, X-XSS-Protection, Accept-Ranges, ETag, Last-Modified, Cache-Control, Expires, Content-Type, Content-Length, Date, Keep-Alive, Connection, Server]
Response Headers: [Strict-Transport-Security, X-Frame-Options, X-Content-Type-Options, X-XSS-Protection, Accept-Ranges, ETag, Last-Modified, Cache-Control, Expires, Content-Type, Content-Length, Date, Keep-Alive, Connection, Server]
Response Headers: [Strict-Transport-Security, X-Frame-Options, X-Content-Type-Options, X-XSS-Protection, Accept-Ranges, ETag, Last-Modified, Cache-Control, Expires, Content-Type, Content-Length, Date, Keep-Alive, Connection, Server]
Response Headers: [Strict-Transport-Security, X-Frame-Options, X-Content-Type-Options, X-XSS-Protection, Accept-Ranges, ETag, Last-Modified, Cache-Control, Expires, Content-Type, Content-Length, Date, Keep-Alive, Connection, Server]
Response Headers: [Strict-Transport-Security, X-Frame-Options, X-Content-Type-Options, X-XSS-Protection, Accept-Ranges, ETag, Last-Modified, Cache-Control, Expires, Content-Type, Content-Length, Date, Keep-Alive, Connection, Server]
Response Headers: [Strict-Transport-Security, X-Frame-Options, X-Content-Type-Options, X-XSS-Protection, Accept-Ranges, ETag, Last-Modified, Cache-Control, Expires, Content-Type, Content-Length, Date, Keep-Alive, Connection, Server]
Response Headers: [Strict-Transport-Security, X-Frame-Options, X-Content-Type-Options, X-XSS-Protection, Accept-Ranges, ETag, Last-Modified, Cache-Control, Expires, Content-Type, Content-Length, Date, Keep-Alive, Connection, Server]
Response Headers: [Strict-Transport-Security, X-Frame-Options, X-Content-Type-Options, X-XSS-Protection, Accept-Ranges, ETag, Last-Modified, Cache-Control, Expires, Content-Type, Content-Length, Date, Keep-Alive, Connection, Server]
Response Headers: [Strict-Transport-Security, X-Frame-Options, X-Content-Type-Options, X-XSS-Protection, Accept-Ranges, ETag, Last-Modified, Cache-Control, Expires, Content-Type, Content-Length, Date, Keep-Alive, Connection, Server]
Response Headers: [Strict-Transport-Security, X-Frame-Options, X-Content-Type-Options, X-XSS-Protection, Accept-Ranges, ETag, Last-Modified, Cache-Control, Expires, Content-Type, Content-Length, Date, Keep-Alive, Connection, Server]
Response Headers: [Strict-Transport-Security, X-Frame-Options, X-Content-Type-Options, X-XSS-Protection, Accept-Ranges, ETag, Last-Modified, Cache-Control, Expires, Content-Type, Content-Length, Date, Keep-Alive, Connection, Server]
Response Headers: [Strict-Transport-Security, X-Frame-Options, X-Content-Type-Options, X-XSS-Protection, Accept-Ranges, ETag, Last-Modified, Cache-Control, Expires, Content-Type, Content-Length, Date, Keep-Alive, Connection, Server]
Response Headers: [Strict-Transport-Security, X-Frame-Options, X-Content-Type-Options, X-XSS-Protection, Accept-Ranges, ETag, Last-Modified, Cache-Control, Expires, Content-Type, Content-Length, Date, Keep-Alive, Connection, Server]
Solution
You can't set headers once the response is committed.
The reason your filter works for some requests and not others is that some requests commit the response before your filter can add the headers and some do not.
If you move the chain.doFilter(request, response);
call to after the code where you set the headers the Filter should apply to everything.
If you want to ensure that the subsequent requests don't change the headers, you have several options. Committing the response is the simplest but it is rather a blunt instrument and may break some of the downstream requests (e.g. any that try to do a RequestDispatcher.forward()
. A better solution is to wrap the response and intercept all the header altering calls and filter out the ones you don't want.
Answered By - Mark Thomas