Securing Web Applications: Implementing Content Security Policy (CSP) in Java

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-Policy HTTP 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

  1. Start with Report-Only Mode:
// Use during development and testing httpResponse.setHeader("Content-Security-Policy-Report-Only", cspPolicy);
  1. 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 } } }
  1. Use Nonces for Dynamic Content:
// Instead of 'unsafe-inline', use nonces for inline scripts/styles "script-src 'self' 'nonce-RANDOM123'"
  1. 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.

2 thoughts on “Securing Web Applications: Implementing Content Security Policy (CSP) in Java”

  1. 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.

  2. 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.

Leave a Reply

Your email address will not be published. Required fields are marked *


Macro Nepal Helper