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.