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 to awiskar acharya Cancel reply

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


Macro Nepal Helper