Strict CSP in a Spring app
by Karen F at Pexels

Strict CSP in a Spring app


spring security java

I’ll let the experts explain CSP, strict CSP, why strict CSP is the current best practice, and the general approach to implementation: MDN / Google

Java request filter and nonce generation

I added the header to each request a typical way: using a OncePerRequestFilter. Since each request needs its own nonce generated uniquely from other requests’, I generate and add it as a request attribute in the same filter.

package me.alexandercalvert.config;

import org.springframework.web.filter.OncePerRequestFilter;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.security.SecureRandom;
import java.util.Base64;

public class ContentSecurityPolicyFilter extends OncePerRequestFilter {

	@Override
	protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
		String nonce = generateNonce();
		response.setHeader("Content-Security-Policy",
					"script-src 'nonce-" + nonce + "' 'strict-dynamic'; " +
						"object-src 'none'; " +
						"base-uri 'none';"
		);
		request.setAttribute("cspNonce", nonce);
		filterChain.doFilter(request, response);
	}

	private String generateNonce() {
		byte[] bytes = new byte[20];
		new SecureRandom().nextBytes(bytes);
		return Base64.getUrlEncoder().withoutPadding().encodeToString(bytes);
	}

}

Configuring a filter isn’t CSP-specific, but to do it I created a bean instance of my filter and registered it with a FilterRegistrationBean in my Spring config:

@Bean	
public ContentSecurityPolicyFilter contentSecurityPolicyFilter() {
	return new ContentSecurityPolicyFilter();
}

@Bean
public FilterRegistrationBean<ContentSecurityPolicyFilter> contentSecurityPolicyFilterRegistrationBean() {
	FilterRegistrationBean<ContentSecurityPolicyFilter> registrationBean = new FilterRegistrationBean<>();
	registrationBean.setFilter(contentSecurityPolicyFilter());
	return registrationBean;
}

Using the nonce on the front end

Since my filter adds the nonce to the request, it’s accessible as a model attribute in the front-end template. I’m using Thymeleaf here; JSTL will work similarly. Notice the th:attr attribute:

<script defer th:src="@{/js/script.js}" th:attr="nonce=${cspNonce}"></script>

That’s all for the interesting part. I refactored all existing inline scripts into their own files and added the nonce attributes to every <script> tag in the application.