Content Security Policy (CSP) is a critical security layer that helps detect and mitigate certain types of attacks, including Cross-Site Scripting (XSS) and data injection attacks. This article provides a comprehensive guide to understanding, implementing, and managing CSP headers in Java web applications, covering everything from basic setup to advanced reporting and monitoring.
Understanding Content Security Policy (CSP)
CSP is a security standard that allows web developers to control which resources the browser is allowed to load for a given page. Instead of trusting everything the server sends, CSP creates a whitelist of trusted content sources and blocks anything not explicitly allowed.
How CSP Works:
- The server sends a
Content-Security-PolicyHTTP header with policy directives - The browser enforces these policies when loading resources
- Violations can be reported back to the server for monitoring
Common Attack Vectors CSP Prevents:
- Cross-Site Scripting (XSS)
- Clickjacking
- Mixed content issues
- Data injection attacks
CSP Directives and Policies
Here are the most commonly used CSP directives:
// Example CSP Policy String cspPolicy = "default-src 'self'; " + "script-src 'self' https://trusted-cdn.com; " + "style-src 'self' 'unsafe-inline'; " + "img-src 'self' data: https:; " + "connect-src 'self'; " + "font-src 'self'; " + "object-src 'none'; " + "frame-ancestors 'none'; " + "base-uri 'self'; " + "form-action 'self'; " + "report-uri /csp-violation-report-endpoint";
Key Directives:
- default-src: Fallback for other resource types
- script-src: Controls JavaScript sources
- style-src: Controls CSS and stylesheet sources
- img-src: Controls image sources
- connect-src: Controls XMLHttpRequest, WebSocket, and EventSource
- font-src: Controls web font sources
- frame-ancestors: Prevents clickjacking
- report-uri: Where to send violation reports
Implementing CSP in Java Applications
1. Servlet Filter Approach (Most Common)
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
public class CspHeaderFilter implements Filter {
private String cspPolicy;
@Override
public void init(FilterConfig filterConfig) {
// Build CSP policy
this.cspPolicy = buildCspPolicy();
}
@Override
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
HttpServletResponse httpResponse = (HttpServletResponse) response;
// Add CSP header
httpResponse.setHeader("Content-Security-Policy", cspPolicy);
// Optional: Add report-only header for testing
// httpResponse.setHeader("Content-Security-Policy-Report-Only", cspPolicy);
chain.doFilter(request, response);
}
private String buildCspPolicy() {
return String.join("; ",
"default-src 'self'",
"script-src 'self' 'unsafe-inline' https://cdnjs.cloudflare.com",
"style-src 'self' 'unsafe-inline' https://fonts.googleapis.com",
"img-src 'self' data: https:",
"font-src 'self' https://fonts.gstatic.com",
"connect-src 'self'",
"object-src 'none'",
"frame-ancestors 'none'",
"base-uri 'self'",
"form-action 'self'",
"report-uri /api/csp-violation"
);
}
@Override
public void destroy() {
// Cleanup if needed
}
}
Web.xml Configuration:
<filter> <filter-name>CspHeaderFilter</filter-name> <filter-class>com.example.CspHeaderFilter</filter-class> </filter> <filter-mapping> <filter-name>CspHeaderFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping>
2. Spring Security Configuration
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.header.writers.StaticHeadersWriter;
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
// Other security configurations
.csrf().disable()
.headers(headers -> headers
.contentSecurityPolicy(csp -> csp
.policyDirectives(buildCspPolicy())
)
.frameOptions().deny()
.xssProtection().block(true)
);
return http.build();
}
private String buildCspPolicy() {
return String.join("; ",
"default-src 'self'",
"script-src 'self' 'nonce-{nonce}' https://trusted-cdn.com",
"style-src 'self' 'unsafe-inline'",
"img-src 'self' data: https:",
"font-src 'self' https://fonts.gstatic.com",
"connect-src 'self'",
"object-src 'none'",
"frame-ancestors 'none'",
"base-uri 'self'",
"form-action 'self'",
"report-uri /api/security/csp-report"
);
}
}
3. Spring Boot with Custom Headers
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class CspConfig implements WebMvcConfigurer {
@Bean
public FilterRegistrationBean<CspHeaderFilter> cspFilter() {
FilterRegistrationBean<CspHeaderFilter> registrationBean = new FilterRegistrationBean<>();
registrationBean.setFilter(new CspHeaderFilter());
registrationBean.addUrlPatterns("/*");
registrationBean.setOrder(1);
return registrationBean;
}
}
Advanced CSP Implementation
Nonce-based CSP for Dynamic Content
import javax.servlet.http.HttpServletRequest;
import java.security.SecureRandom;
import java.util.Base64;
public class NonceGenerator {
private static final SecureRandom secureRandom = new SecureRandom();
private static final Base64.Encoder base64Encoder = Base64.getEncoder();
public static String generateNonce() {
byte[] randomBytes = new byte[16];
secureRandom.nextBytes(randomBytes);
return base64Encoder.encodeToString(randomBytes);
}
public static String addNonceToCsp(String cspPolicy, String nonce) {
return cspPolicy.replace("{nonce}", "'nonce-" + nonce + "'");
}
}
// Enhanced CSP Filter with Nonce Support
public class NonceCspFilter implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
HttpServletRequest httpRequest = (HttpServletRequest) request;
HttpServletResponse httpResponse = (HttpServletResponse) response;
// Generate nonce for this request
String nonce = NonceGenerator.generateNonce();
// Store nonce in request for use in JSP/templates
httpRequest.setAttribute("cspNonce", nonce);
// Build CSP policy with nonce
String cspPolicy = buildCspPolicy(nonce);
httpResponse.setHeader("Content-Security-Policy", cspPolicy);
chain.doFilter(request, response);
}
private String buildCspPolicy(String nonce) {
return String.join("; ",
"default-src 'self'",
"script-src 'self' 'nonce-" + nonce + "' https://trusted-cdn.com",
"style-src 'self' 'unsafe-inline'",
"img-src 'self' data: https:",
"font-src 'self'",
"connect-src 'self'",
"object-src 'none'",
"frame-ancestors 'none'",
"report-uri /api/csp-violation"
);
}
}
Using Nonce in JSP:
<script nonce="${cspNonce}">
// This inline script will execute because it has a valid nonce
console.log('Secure inline script');
</script>
<script src="https://trusted-cdn.com/library.js"></script>
CSP Violation Reporting
Violation Report Endpoint
import org.springframework.web.bind.annotation.*;
import javax.servlet.http.HttpServletRequest;
import java.io.BufferedReader;
@RestController
@RequestMapping("/api/security")
public class CspReportController {
@PostMapping("/csp-report")
public void handleCspViolation(HttpServletRequest request) {
try {
// Read the violation report
StringBuilder reportBuilder = new StringBuilder();
try (BufferedReader reader = request.getReader()) {
String line;
while ((line = reader.readLine()) != null) {
reportBuilder.append(line);
}
}
String violationReport = reportBuilder.toString();
// Log the violation (in production, send to monitoring system)
logViolation(violationReport);
} catch (Exception e) {
// Don't throw exceptions from violation reports
System.err.println("Error processing CSP violation: " + e.getMessage());
}
}
private void logViolation(String violationReport) {
// In production, send to your logging/monitoring system
System.out.println("CSP Violation: " + violationReport);
// Example: Send to log file, SIEM, or security monitoring service
// logger.warn("CSP Violation detected: {}", violationReport);
}
}
Violation Report Model
import com.fasterxml.jackson.annotation.JsonProperty;
import java.time.Instant;
public class CspViolationReport {
@JsonProperty("csp-report")
private CspReport cspReport;
// Getters and setters
public static class CspReport {
private String documentUri;
private String referrer;
private String blockedUri;
private String violatedDirective;
private String effectiveDirective;
private String originalPolicy;
private String disposition;
private String scriptSample;
private int statusCode;
private String sourceFile;
private int lineNumber;
private int columnNumber;
// Getters and setters
public String getDocumentUri() { return documentUri; }
public void setDocumentUri(String documentUri) { this.documentUri = documentUri; }
public String getBlockedUri() { return blockedUri; }
public void setBlockedUri(String blockedUri) { this.blockedUri = blockedUri; }
public String getViolatedDirective() { return violatedDirective; }
public void setViolatedDirective(String violatedDirective) { this.violatedDirective = violatedDirective; }
// ... other getters and setters
}
}
Environment-Specific CSP Policies
public class CspPolicyBuilder {
public enum Environment {
DEVELOPMENT, STAGING, PRODUCTION
}
public static String buildPolicy(Environment env, String nonce) {
switch (env) {
case DEVELOPMENT:
return buildDevelopmentPolicy(nonce);
case STAGING:
return buildStagingPolicy(nonce);
case PRODUCTION:
return buildProductionPolicy(nonce);
default:
return buildStrictPolicy(nonce);
}
}
private static String buildDevelopmentPolicy(String nonce) {
// More permissive for development
return String.join("; ",
"default-src 'self' 'unsafe-eval'", // Allow eval for development
"script-src 'self' 'unsafe-eval' 'nonce-" + nonce + "'",
"style-src 'self' 'unsafe-inline'",
"img-src 'self' data: https:",
"connect-src 'self' ws: wss:", // Allow WebSockets
"font-src 'self'",
"object-src 'none'",
"frame-ancestors 'none'",
"report-uri /api/csp-violation"
);
}
private static String buildProductionPolicy(String nonce) {
// Strict policy for production
return String.join("; ",
"default-src 'self'",
"script-src 'self' 'nonce-" + nonce + "' https://cdn.example.com",
"style-src 'self'",
"img-src 'self' data: https:",
"connect-src 'self' https://api.example.com",
"font-src 'self'",
"object-src 'none'",
"frame-ancestors 'none'",
"base-uri 'self'",
"form-action 'self'",
"report-uri /api/csp-violation"
);
}
}
Testing CSP Implementation
JUnit Test for CSP Headers
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.web.servlet.MockMvc;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header;
@SpringBootTest
@AutoConfigureMockMvc
public class CspHeaderTest {
@Autowired
private MockMvc mockMvc;
@Test
void testCspHeaderPresent() throws Exception {
mockMvc.perform(get("/"))
.andExpect(header().exists("Content-Security-Policy"))
.andExpect(header().string("Content-Security-Policy",
org.hamcrest.Matchers.containsString("default-src 'self'")));
}
@Test
void testCspHeaderContainsExpectedDirectives() throws Exception {
mockMvc.perform(get("/"))
.andExpect(header().string("Content-Security-Policy",
org.hamcrest.Matchers.allOf(
org.hamcrest.Matchers.containsString("script-src"),
org.hamcrest.Matchers.containsString("style-src"),
org.hamcrest.Matchers.containsString("object-src 'none'")
)));
}
}
Integration Test for Violation Reporting
@Test
void testCspViolationReporting() throws Exception {
String violationReport = """
{
"csp-report": {
"document-uri": "https://example.com/page",
"blocked-uri": "https://malicious.com/script.js",
"violated-directive": "script-src",
"original-policy": "script-src 'self'"
}
}
""";
mockMvc.perform(post("/api/security/csp-report")
.contentType("application/csp-report")
.content(violationReport))
.andExpect(status().isOk());
}
Best Practices for CSP in Java
- Start with Report-Only Mode:
// Use during development and testing
httpResponse.setHeader("Content-Security-Policy-Report-Only", cspPolicy);
- Implement Progressive Security:
public class ProgressiveCspStrategy {
public String getCspPolicy(HttpServletRequest request) {
if (isNewUser(request)) {
return buildPermissivePolicy(); // Learn what resources are needed
} else {
return buildStrictPolicy(); // Enforce strict policy
}
}
}
- Use Nonces for Dynamic Content:
// Instead of 'unsafe-inline', use nonces for inline scripts/styles "script-src 'self' 'nonce-RANDOM123'"
- Monitor and Adapt:
@Component
public class CspViolationMonitor {
@EventListener
public void handleCspViolation(CspViolationEvent event) {
// Analyze patterns and adjust policies
if (isCommonFalsePositive(event)) {
adjustPolicyForLegacyFeatures();
}
}
}
Common CSP Challenges and Solutions
Challenge 1: Third-party Widgets
// Solution: Allow specific third-party sources
String cspForThirdParty = String.join("; ",
"default-src 'self'",
"script-src 'self' https://widgets.example.com",
"frame-src https://embed.example.com",
"connect-src 'self' https://api.thirdparty.com"
);
Challenge 2: Legacy Applications
// Gradual implementation strategy
public class GradualCspFilter implements Filter {
private int implementationPhase = 1;
private String getPolicyForPhase() {
switch (implementationPhase) {
case 1: return buildReportOnlyPolicy();
case 2: return buildPermissiveEnforcingPolicy();
case 3: return buildStrictPolicy();
default: return buildStrictPolicy();
}
}
}
Conclusion
Implementing Content Security Policy in Java applications provides a powerful defense against XSS and other code injection attacks. By following these practices:
- Start with reporting to identify needed resources
- Use nonces for dynamic content instead of
unsafe-inline - Implement environment-specific policies
- Monitor violations and continuously refine policies
- Use frameworks like Spring Security for consistent implementation
CSP significantly enhances your application's security posture. Remember that CSP is not a one-time setup but an ongoing process of monitoring, analysis, and policy refinement as your application evolves.
What would you use to build a java web app with strict csp? Meaning no unsafe-inline, no use of evals, etc. I was trying to use vaadin, but it somehow seems to be harder than it should.
For a Java web app with strict Content Security Policy (no `unsafe-inline`, no `eval`), Spring Boot with Thymeleaf is the best choice—it avoids inline scripts/styles by default and lets you easily set CSP headers via Spring Security or servlet filters. You can also consider JHipster (Spring + Angular), which supports CSP with build tweaks. Vaadin is possible but more complex, as it injects inline styles/scripts unless you enable CSP mode and refactor components to use external resources. To enforce CSP, configure headers like `Content-Security-Policy: default-src ‘self’; script-src ‘self’; style-src ‘self’;` and ensure all scripts and styles are loaded externally.