浏览代码

Adds support for Content Security Policy

Fixes gh-2342
Joe Grandja 9 年之前
父节点
当前提交
2f7f2ff589

+ 63 - 5
config/src/main/java/org/springframework/security/config/annotation/web/configurers/HeadersConfigurer.java

@@ -27,11 +27,7 @@ import org.springframework.security.config.annotation.web.builders.HttpSecurity;
 import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
 import org.springframework.security.web.header.HeaderWriter;
 import org.springframework.security.web.header.HeaderWriterFilter;
-import org.springframework.security.web.header.writers.CacheControlHeadersWriter;
-import org.springframework.security.web.header.writers.HpkpHeaderWriter;
-import org.springframework.security.web.header.writers.HstsHeaderWriter;
-import org.springframework.security.web.header.writers.XContentTypeOptionsHeaderWriter;
-import org.springframework.security.web.header.writers.XXssProtectionHeaderWriter;
+import org.springframework.security.web.header.writers.*;
 import org.springframework.security.web.header.writers.frameoptions.XFrameOptionsHeaderWriter;
 import org.springframework.security.web.header.writers.frameoptions.XFrameOptionsHeaderWriter.XFrameOptionsMode;
 import org.springframework.security.web.util.matcher.RequestMatcher;
@@ -59,6 +55,7 @@ import org.springframework.util.Assert;
  *
  * @author Rob Winch
  * @author Tim Ysewyn
+ * @author Joe Grandja
  * @since 3.2
  */
 public class HeadersConfigurer<H extends HttpSecurityBuilder<H>> extends
@@ -79,6 +76,8 @@ public class HeadersConfigurer<H extends HttpSecurityBuilder<H>> extends
 
 	private final HpkpConfig hpkp = new HpkpConfig();
 
+	private final ContentSecurityPolicyConfig contentSecurityPolicy = new ContentSecurityPolicyConfig();
+
 	/**
 	 * Creates a new instance
 	 *
@@ -657,6 +656,64 @@ public class HeadersConfigurer<H extends HttpSecurityBuilder<H>> extends
 		}
 	}
 
+	/**
+	 * <p>
+	 * Allows configuration for <a href="https://www.w3.org/TR/CSP2/">Content Security Policy (CSP) Level 2</a>.
+	 * </p>
+	 *
+	 * <p>
+	 * Calling this method automatically enables (includes) the Content-Security-Policy header in the response
+	 * using the supplied security policy directive(s).
+	 * </p>
+	 *
+	 * <p>
+	 * Configuration is provided to the {@link ContentSecurityPolicyHeaderWriter} which supports the writing
+	 * of the two headers as detailed in the W3C Candidate Recommendation:
+	 * </p>
+	 * <ul>
+	 * 	<li>Content-Security-Policy</li>
+	 * 	<li>Content-Security-Policy-Report-Only</li>
+	 * </ul>
+	 *
+	 * @see ContentSecurityPolicyHeaderWriter
+	 * @since 4.1
+	 * @return the ContentSecurityPolicyConfig for additional configuration
+	 * @throws IllegalArgumentException if policyDirectives is null or empty
+	 */
+	public ContentSecurityPolicyConfig contentSecurityPolicy(String policyDirectives) {
+		this.contentSecurityPolicy.writer =
+				new ContentSecurityPolicyHeaderWriter(policyDirectives);
+		return contentSecurityPolicy;
+	}
+
+	public final class ContentSecurityPolicyConfig {
+		private ContentSecurityPolicyHeaderWriter writer;
+
+		private ContentSecurityPolicyConfig() {
+		}
+
+		/**
+		 * Enables (includes) the Content-Security-Policy-Report-Only header in the response.
+		 *
+		 * @return the {@link ContentSecurityPolicyConfig} for additional configuration
+		 */
+		public ContentSecurityPolicyConfig reportOnly() {
+			this.writer.setReportOnly(true);
+			return this;
+		}
+
+		/**
+		 * Allows completing configuration of Content Security Policy and continuing
+		 * configuration of headers.
+		 *
+		 * @return the {@link HeadersConfigurer} for additional configuration
+		 */
+		public HeadersConfigurer<H> and() {
+			return HeadersConfigurer.this;
+		}
+
+	}
+
 	/**
 	 * Clears all of the default headers from the response. After doing so, one can add
 	 * headers back. For example, if you only want to use Spring Security's cache control
@@ -712,6 +769,7 @@ public class HeadersConfigurer<H extends HttpSecurityBuilder<H>> extends
 		addIfNotNull(writers, hsts.writer);
 		addIfNotNull(writers, frameOptions.writer);
 		addIfNotNull(writers, hpkp.writer);
+		addIfNotNull(writers, contentSecurityPolicy.writer);
 		writers.addAll(headerWriters);
 		return writers;
 	}

+ 33 - 0
config/src/main/java/org/springframework/security/config/http/HeadersBeanDefinitionParser.java

@@ -67,6 +67,7 @@ public class HeadersBeanDefinitionParser implements BeanDefinitionParser {
 	private static final String ATT_REPORT_ONLY = "report-only";
 	private static final String ATT_REPORT_URI = "report-uri";
 	private static final String ATT_ALGORITHM = "algorithm";
+	private static final String ATT_POLICY_DIRECTIVES = "policy-directives";
 
 	private static final String CACHE_CONTROL_ELEMENT = "cache-control";
 
@@ -80,6 +81,8 @@ public class HeadersBeanDefinitionParser implements BeanDefinitionParser {
 	private static final String FRAME_OPTIONS_ELEMENT = "frame-options";
 	private static final String GENERIC_HEADER_ELEMENT = "header";
 
+	private static final String CONTENT_SECURITY_POLICY_ELEMENT = "content-security-policy";
+
 	private static final String ALLOW_FROM = "ALLOW-FROM";
 
 	private ManagedList<BeanMetadataElement> headerWriters;
@@ -104,6 +107,8 @@ public class HeadersBeanDefinitionParser implements BeanDefinitionParser {
 
 		parseHpkpElement(element == null || !disabled, element, parserContext);
 
+		parseContentSecurityPolicyElement(disabled, element, parserContext);
+
 		parseHeaderElements(element);
 
 		if (disabled) {
@@ -258,6 +263,34 @@ public class HeadersBeanDefinitionParser implements BeanDefinitionParser {
 		}
 	}
 
+	private void parseContentSecurityPolicyElement(boolean elementDisabled, Element element, ParserContext context) {
+		Element contentSecurityPolicyElement = (elementDisabled || element == null) ? null : DomUtils.getChildElementByTagName(
+				element, CONTENT_SECURITY_POLICY_ELEMENT);
+		if (contentSecurityPolicyElement != null) {
+			addContentSecurityPolicy(contentSecurityPolicyElement, context);
+		}
+	}
+
+	private void addContentSecurityPolicy(Element contentSecurityPolicyElement, ParserContext context) {
+		BeanDefinitionBuilder headersWriter = BeanDefinitionBuilder
+				.genericBeanDefinition(ContentSecurityPolicyHeaderWriter.class);
+
+		String policyDirectives = contentSecurityPolicyElement.getAttribute(ATT_POLICY_DIRECTIVES);
+		if (!StringUtils.hasText(policyDirectives)) {
+			context.getReaderContext().error(
+					ATT_POLICY_DIRECTIVES + " requires a 'value' to be set.", contentSecurityPolicyElement);
+		} else {
+			headersWriter.addConstructorArgValue(policyDirectives);
+		}
+
+		String reportOnly = contentSecurityPolicyElement.getAttribute(ATT_REPORT_ONLY);
+		if (StringUtils.hasText(reportOnly)) {
+			headersWriter.addPropertyValue("reportOnly", reportOnly);
+		}
+
+		headerWriters.add(headersWriter.getBeanDefinition());
+	}
+
 	private void attrNotAllowed(ParserContext context, String attrName,
 			String otherAttrName, Element element) {
 		context.getReaderContext().error(

+ 11 - 1
config/src/main/resources/org/springframework/security/config/spring-security-4.1.rnc

@@ -748,7 +748,7 @@ csrf-options.attlist &=
 
 headers =
 ## Element for configuration of the HeaderWritersFilter. Enables easy setting for the X-Frame-Options, X-XSS-Protection and X-Content-Type-Options headers.
-element headers { headers-options.attlist, (cache-control? & xss-protection? & hsts? & frame-options? & content-type-options? & hpkp? & header*)}
+element headers { headers-options.attlist, (cache-control? & xss-protection? & hsts? & frame-options? & content-type-options? & hpkp? & content-security-policy? & header*)}
 headers-options.attlist &=
 	## Specifies if the default headers should be disabled. Default false.
 	attribute defaults-disabled {xsd:boolean}?
@@ -800,6 +800,16 @@ hpkp.attlist &=
 	## Specifies the URI to which the browser should report pin validation failures.
 	attribute report-uri {xsd:string}?
 
+content-security-policy =
+	## Adds support for Content Security Policy (CSP)
+	element content-security-policy {csp-options.attlist}
+csp-options.attlist &=
+	## The security policy directive(s) for the Content-Security-Policy header or if report-only is set to true, then the Content-Security-Policy-Report-Only header is used.
+	attribute policy-directives {xsd:token}?
+csp-options.attlist &=
+	## Set to true, to enable the Content-Security-Policy-Report-Only header for reporting policy violations only. Defaults to false.
+	attribute report-only {xsd:boolean}?
+
 cache-control =
 	## Adds Cache-Control no-cache, no-store, must-revalidate, Pragma no-cache, and Expires 0 for every request
 	element cache-control {cache-control.attlist}

+ 26 - 0
config/src/main/resources/org/springframework/security/config/spring-security-4.1.xsd

@@ -2328,6 +2328,7 @@
             <xs:element ref="security:frame-options"/>
             <xs:element ref="security:content-type-options"/>
             <xs:element ref="security:hpkp"/>
+            <xs:element ref="security:content-security-policy"/>
             <xs:element ref="security:header"/>
          </xs:choice>
          <xs:attributeGroup ref="security:headers-options.attlist"/>
@@ -2460,6 +2461,31 @@
          </xs:annotation>
       </xs:attribute>
   </xs:attributeGroup>
+  <xs:element name="content-security-policy">
+      <xs:annotation>
+         <xs:documentation>Adds support for Content Security Policy (CSP)
+                </xs:documentation>
+      </xs:annotation>
+      <xs:complexType>
+         <xs:attributeGroup ref="security:csp-options.attlist"/>
+      </xs:complexType>
+   </xs:element>
+  <xs:attributeGroup name="csp-options.attlist">
+      <xs:attribute name="policy-directives" type="xs:token">
+         <xs:annotation>
+            <xs:documentation>The security policy directive(s) for the Content-Security-Policy header or if report-only
+                is set to true, then the Content-Security-Policy-Report-Only header is used.
+                </xs:documentation>
+         </xs:annotation>
+      </xs:attribute>
+      <xs:attribute name="report-only" type="xs:boolean">
+         <xs:annotation>
+            <xs:documentation>Set to true, to enable the Content-Security-Policy-Report-Only header for reporting policy
+                violations only. Defaults to false.
+                </xs:documentation>
+         </xs:annotation>
+      </xs:attribute>
+  </xs:attributeGroup>
   <xs:element name="cache-control">
       <xs:annotation>
          <xs:documentation>Adds Cache-Control no-cache, no-store, must-revalidate, Pragma no-cache, and Expires 0 for

+ 66 - 0
config/src/test/groovy/org/springframework/security/config/annotation/web/configurers/HeadersConfigurerTests.groovy

@@ -15,6 +15,7 @@
  */
 package org.springframework.security.config.annotation.web.configurers
 
+import org.springframework.beans.factory.BeanCreationException
 import org.springframework.security.config.annotation.BaseSpringSpec
 import org.springframework.security.config.annotation.web.builders.HttpSecurity
 import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity
@@ -24,6 +25,7 @@ import org.springframework.security.config.annotation.web.configuration.WebSecur
  *
  * @author Rob Winch
  * @author Tim Ysewyn
+ * @author Joe Grandja
  */
 class HeadersConfigurerTests extends BaseSpringSpec {
 
@@ -387,4 +389,68 @@ class HeadersConfigurerTests extends BaseSpringSpec {
 					.reportUri("http://example.net/pkp-report")
 		}
 	}
+
+	def "headers.contentSecurityPolicy default header"() {
+		setup:
+			loadConfig(ContentSecurityPolicyDefaultConfig)
+			request.secure = true
+		when:
+			springSecurityFilterChain.doFilter(request,response,chain)
+		then:
+			responseHeaders == ['Content-Security-Policy': 'default-src \'self\'']
+	}
+
+	@EnableWebSecurity
+	static class ContentSecurityPolicyDefaultConfig extends WebSecurityConfigurerAdapter {
+
+		@Override
+		protected void configure(HttpSecurity http) throws Exception {
+			http
+					.headers()
+					.defaultsDisabled()
+					.contentSecurityPolicy("default-src 'self'");
+		}
+	}
+
+	def "headers.contentSecurityPolicy report-only header"() {
+		setup:
+			loadConfig(ContentSecurityPolicyReportOnlyConfig)
+			request.secure = true
+		when:
+			springSecurityFilterChain.doFilter(request,response,chain)
+		then:
+			responseHeaders == ['Content-Security-Policy-Report-Only': 'default-src \'self\'; script-src trustedscripts.example.com']
+	}
+
+	@EnableWebSecurity
+	static class ContentSecurityPolicyReportOnlyConfig extends WebSecurityConfigurerAdapter {
+
+		@Override
+		protected void configure(HttpSecurity http) throws Exception {
+			http
+					.headers()
+					.defaultsDisabled()
+					.contentSecurityPolicy("default-src 'self'; script-src trustedscripts.example.com").reportOnly();
+		}
+	}
+
+	def "headers.contentSecurityPolicy empty policyDirectives"() {
+		when:
+			loadConfig(ContentSecurityPolicyInvalidConfig)
+		then:
+			thrown(BeanCreationException)
+	}
+
+	@EnableWebSecurity
+	static class ContentSecurityPolicyInvalidConfig extends WebSecurityConfigurerAdapter {
+
+		@Override
+		protected void configure(HttpSecurity http) throws Exception {
+			http
+					.headers()
+					.defaultsDisabled()
+					.contentSecurityPolicy("");
+		}
+	}
+
 }

+ 78 - 0
config/src/test/groovy/org/springframework/security/config/http/HttpHeadersConfigTests.groovy

@@ -830,6 +830,84 @@ class HttpHeadersConfigTests extends AbstractHttpConfigTests {
 			expected.message.contains 'policy'
 	}
 
+	def 'http headers defaults : content-security-policy'() {
+		setup:
+			httpAutoConfig {
+				'headers'() {
+					'content-security-policy'('policy-directives':'default-src \'self\'')
+				}
+			}
+			createAppContext()
+		when:
+			def hf = getFilter(HeaderWriterFilter)
+			MockHttpServletResponse response = new MockHttpServletResponse()
+			hf.doFilter(new MockHttpServletRequest(secure:true), response, new MockFilterChain())
+			def expectedHeaders = [:] << defaultHeaders
+			expectedHeaders['Content-Security-Policy'] = 'default-src \'self\''
+		then:
+			assertHeaders(response, expectedHeaders)
+	}
+
+	def 'http headers disabled : content-security-policy not included'() {
+		setup:
+			httpAutoConfig {
+				'headers'(disabled:true) {
+					'content-security-policy'('policy-directives':'default-src \'self\'')
+				}
+			}
+			createAppContext()
+		when:
+			def hf = getFilter(HeaderWriterFilter)
+		then:
+			!hf
+	}
+
+	def 'http headers defaults disabled : content-security-policy only'() {
+		setup:
+			httpAutoConfig {
+				'headers'('defaults-disabled':true) {
+					'content-security-policy'('policy-directives':'default-src \'self\'')
+				}
+			}
+			createAppContext()
+		when:
+			def hf = getFilter(HeaderWriterFilter)
+			MockHttpServletResponse response = new MockHttpServletResponse()
+			hf.doFilter(new MockHttpServletRequest(secure:true), response, new MockFilterChain())
+		then:
+			assertHeaders(response, ['Content-Security-Policy':'default-src \'self\''])
+	}
+
+	def 'http headers defaults : content-security-policy with empty directives'() {
+		when:
+			httpAutoConfig {
+				'headers'() {
+					'content-security-policy'('policy-directives':'')
+				}
+			}
+			createAppContext()
+		then:
+			thrown(BeanDefinitionParsingException)
+	}
+
+	def 'http headers defaults : content-security-policy report-only=true'() {
+		setup:
+			httpAutoConfig {
+				'headers'() {
+					'content-security-policy'('policy-directives':'default-src https:; report-uri https://example.com/', 'report-only':true)
+				}
+			}
+			createAppContext()
+		when:
+			def hf = getFilter(HeaderWriterFilter)
+			MockHttpServletResponse response = new MockHttpServletResponse()
+			hf.doFilter(new MockHttpServletRequest(secure:true), response, new MockFilterChain())
+			def expectedHeaders = [:] << defaultHeaders
+			expectedHeaders['Content-Security-Policy-Report-Only'] = 'default-src https:; report-uri https://example.com/'
+		then:
+			assertHeaders(response, expectedHeaders)
+	}
+
 	def assertHeaders(MockHttpServletResponse response, Map<String,String> expected) {
 		assert response.headerNames == expected.keySet()
 		expected.each { headerName, value ->

+ 176 - 11
docs/manual/src/docs/asciidoc/index.adoc

@@ -3825,7 +3825,7 @@ Allowing your website to be added to a frame can be a security issue. For exampl
 
 [NOTE]
 ====
-Another modern approach to dealing with clickjacking is using a http://www.w3.org/TR/CSP/[Content Security Policy]. Spring Security does not provide support for this as the specification is not released and it is quite a bit more complicated. However, you could use the <<headers-static,static headers>> feature to implement this. To stay up to date with this issue and to see how you can implement it with Spring Security refer to https://jira.spring.io/browse/SEC-2117[SEC-2117]
+Another modern approach to dealing with clickjacking is to use <<headers-content-security-policy>>.
 ====
 
 There are a number ways to mitigate clickjacking attacks. For example, to protect legacy browsers from clickjacking attacks you can use https://www.owasp.org/index.php/Clickjacking_Defense_Cheat_Sheet#Best-for-now_Legacy_Browser_Frame_Breaking_Script[frame breaking code]. While not perfect, the frame breaking code is the best you can do for the legacy browsers.
@@ -3917,6 +3917,153 @@ protected void configure(HttpSecurity http) throws Exception {
 }
 ----
 
+[[headers-content-security-policy]]
+==== Content Security Policy (CSP)
+
+https://www.w3.org/TR/CSP2/[Content Security Policy (CSP)] is a mechanism that web applications can leverage to mitigate content injection vulnerabilities,
+such as cross-site scripting (XSS). CSP is a declarative policy that provides a facility for web application authors to declare and ultimately inform
+the client (user-agent) about the sources from which the web application expects to load resources.
+
+[NOTE]
+====
+Content Security Policy is not intended to solve all content injection vulnerabilities.
+Instead, CSP can be leveraged to help reduce the harm caused by content injection attacks.
+As a first line of defense, web application authors should validate their input and encode their output.
+====
+
+A web application may employ the use of CSP by including one of the following HTTP headers in the response:
+
+* *_Content-Security-Policy_*
+* *_Content-Security-Policy-Report-Only_*
+
+Each of these headers are used as a mechanism to deliver a *_security policy_* to the client.
+A security policy contains a set of *_security policy directives_* (for example, _script-src_ and _object-src_),
+each responsible for declaring the restrictions for a particular resource representation.
+
+For example, a web application can declare that it expects to load scripts from specific, trusted sources,
+by including the following header in the response:
+
+[source]
+----
+Content-Security-Policy: script-src https://trustedscripts.example.com
+----
+
+An attempt to load a script from another source other than what is declared in the _script-src_ directive will be blocked by the user-agent.
+Additionally, if the https://www.w3.org/TR/CSP2/#directive-report-uri[*_report-uri_*] directive is declared in the security policy,
+then the violation will be reported by the user-agent to the declared URL.
+
+For example, if a web application violates the declared security policy,
+the following response header will instruct the user-agent to send violation reports to the URL specified in the policy’s _report-uri_ directive.
+
+[source]
+----
+Content-Security-Policy: script-src https://trustedscripts.example.com; report-uri /csp-report-endpoint/
+----
+
+The *_Content-Security-Policy-Report-Only_* header provides the capability for web application authors and administrators to monitor security policies, rather than enforce them.
+This header is typically used when experimenting and/or developing security policies for a site.
+When a policy is deemed effective, it can be enforced by using the _Content-Security-Policy_ header field instead.
+
+Given the following response header, the policy declares that scripts may be loaded from one of two possible sources.
+
+[source]
+----
+Content-Security-Policy-Report-Only: script-src 'self' https://trustedscripts.example.com; report-uri /csp-report-endpoint/
+----
+
+If the site violates this policy, by attempting to load a script from _evil.com_,
+the user-agent will send a violation report to the declared URL specified by the _report-uri_ directive,
+but still allow the violating resource to load nevertheless.
+
+===== Configuring Content Security Policy
+
+It's important to note that Spring Security *_does not add_* Content Security Policy by default.
+The web application author must declare the security policy(s) to enforce and/or monitor for the protected resources.
+
+For example, given the following security policy:
+
+[source]
+----
+script-src 'self' https://trustedscripts.example.com; object-src https://trustedplugins.example.com; report-uri /csp-report-endpoint/
+----
+
+You can enable the CSP header using XML configuration with the <<nsa-content-security-policy,<content-security-policy>>> element as shown below:
+
+[source,xml]
+----
+<http>
+	<!-- ... -->
+
+	<headers>
+		<content-security-policy
+			policy-directives="script-src 'self' https://trustedscripts.example.com; object-src https://trustedplugins.example.com; report-uri /csp-report-endpoint/" />
+	</headers>
+</http>
+----
+
+To enable the CSP _'report-only'_ header, configure the element as follows:
+
+[source,xml]
+----
+<http>
+	<!-- ... -->
+
+	<headers>
+		<content-security-policy
+			policy-directives="script-src 'self' https://trustedscripts.example.com; object-src https://trustedplugins.example.com; report-uri /csp-report-endpoint/"
+			report-only="true" />
+	</headers>
+</http>
+----
+
+Similarly, you can enable the CSP header using Java configuration as shown below:
+
+[source,java]
+----
+@EnableWebSecurity
+public class WebSecurityConfig extends
+WebSecurityConfigurerAdapter {
+
+@Override
+protected void configure(HttpSecurity http) throws Exception {
+	http
+	// ...
+	.headers()
+		.contentSecurityPolicy("script-src 'self' https://trustedscripts.example.com; object-src https://trustedplugins.example.com; report-uri /csp-report-endpoint/");
+}
+}
+----
+
+To enable the CSP _'report-only'_ header, provide the following Java configuration:
+
+[source,java]
+----
+@EnableWebSecurity
+public class WebSecurityConfig extends
+WebSecurityConfigurerAdapter {
+
+@Override
+protected void configure(HttpSecurity http) throws Exception {
+	http
+	// ...
+	.headers()
+		.contentSecurityPolicy("script-src 'self' https://trustedscripts.example.com; object-src https://trustedplugins.example.com; report-uri /csp-report-endpoint/")
+		.reportOnly();
+}
+}
+----
+
+===== Additional Resources
+
+Applying Content Security Policy to a web application is often a non-trivial undertaking.
+The following resources may provide further assistance in developing effective security policies for your site.
+
+http://www.html5rocks.com/en/tutorials/security/content-security-policy/[An Introduction to Content Security Policy]
+
+https://developer.mozilla.org/en-US/docs/Web/Security/CSP[CSP Guide - Mozilla Developer Network]
+
+https://www.w3.org/TR/CSP2/[W3C Candidate Recommendation]
+
 [[headers-custom]]
 === Custom Headers
 Spring Security has mechanisms to make it convenient to add the more common security headers to your application. However, it also provides hooks to enable adding custom headers.
@@ -3924,15 +4071,11 @@ Spring Security has mechanisms to make it convenient to add the more common secu
 [[headers-static]]
 ==== Static Headers
 There may be times you wish to inject custom security headers into your application that are not supported out of the box.
-For example, perhaps you wish to have early support for http://www.w3.org/TR/CSP/[Content Security Policy] in order to ensure that resources are only loaded from the same origin.
-Since support for Content Security Policy has not been finalized, browsers use one of two common extension headers to implement the feature.
-This means we will need to inject the policy twice.
-An example of the headers can be seen below:
+For example, given the following custom security header:
 
 [source]
 ----
-X-Content-Security-Policy: default-src 'self'
-X-WebKit-CSP: default-src 'self'
+X-Custom-Security-Header: header-value
 ----
 
 When using the XML namespace, these headers can be added to the response using the <<nsa-header,<header>>> element as shown below:
@@ -3943,8 +4086,7 @@ When using the XML namespace, these headers can be added to the response using t
 	<!-- ... -->
 
 	<headers>
-		<header name="X-Content-Security-Policy" value="default-src 'self'"/>
-		<header name="X-WebKit-CSP" value="default-src 'self'"/>
+		<header name="X-Custom-Security-Header" value="header-value"/>
 	</headers>
 </http>
 ----
@@ -3962,8 +4104,7 @@ protected void configure(HttpSecurity http) throws Exception {
 	http
 	// ...
 	.headers()
-		.addHeaderWriter(new StaticHeadersWriter("X-Content-Security-Policy","default-src 'self'"))
-		.addHeaderWriter(new StaticHeadersWriter("X-WebKit-CSP","default-src 'self'"));
+		.addHeaderWriter(new StaticHeadersWriter("X-Custom-Security-Header","header-value"));
 }
 }
 ----
@@ -7076,6 +7217,7 @@ This element allows for configuring additional (security) headers to be send wit
 ** `X-XSS-Protection` - Can be set using the <<nsa-xss-protection,xss-protection>> element. The http://en.wikipedia.org/wiki/Cross-site_scripting[X-XSS-Protection ] header can be used by browser to do basic control.
 ** `X-Content-Type-Options` - Can be set using the <<nsa-content-type-options,content-type-options>> element. The http://blogs.msdn.com/b/ie/archive/2008/09/02/ie8-security-part-vi-beta-2-update.aspx[X-Content-Type-Options] header prevents Internet Explorer from MIME-sniffing a response away from the declared content-type. This also applies to Google Chrome, when downloading extensions.
 ** `Public-Key-Pinning` or `Public-Key-Pinning-Report-Only` - Can be set using the <<nsa-hpkp,hpkp>> element. This allows HTTPS websites to resist impersonation by attackers using mis-issued or otherwise fraudulent certificates.
+** `Content-Security-Policy` or `Content-Security-Policy-Report-Only` - Can be set using the <<nsa-content-security-policy,content-security-policy>> element. https://www.w3.org/TR/CSP2/[Content Security Policy (CSP)] is a mechanism that web applications can leverage to mitigate content injection vulnerabilities, such as cross-site scripting (XSS).
 
 [[nsa-headers-attributes]]
 ===== <headers> Attributes
@@ -7103,6 +7245,7 @@ Optional attribute that specifies to disable Spring Security's HTTP response hea
 
 
 * <<nsa-cache-control,cache-control>>
+* <<nsa-content-security-policy,content-security-policy>>
 * <<nsa-content-type-options,content-type-options>>
 * <<nsa-frame-options,frame-options>>
 * <<nsa-header,header>>
@@ -7235,6 +7378,28 @@ The cryptographic hash algorithm. Default is SHA256.
 
 
 
+[[nsa-content-security-policy]]
+==== <content-security-policy>
+When enabled adds the https://www.w3.org/TR/CSP2/[Content Security Policy (CSP)] header to the response. CSP is a mechanism that web applications can leverage to mitigate content injection vulnerabilities, such as cross-site scripting (XSS).
+
+[[nsa-content-security-policy-attributes]]
+===== <content-security-policy> Attributes
+
+[[nsa-content-security-policy-policy-directives]]
+* **policy-directives**
+The security policy directive(s) for the Content-Security-Policy header or if report-only is set to true, then the Content-Security-Policy-Report-Only header is used.
+
+[[nsa-content-security-policy-report-only]]
+* **report-only**
+Set to true, to enable the Content-Security-Policy-Report-Only header for reporting policy violations only. Defaults to false.
+
+[[nsa-content-security-policy-parents]]
+===== Parent Elements of <content-security-policy>
+
+* <<nsa-headers,headers>>
+
+
+
 [[nsa-frame-options]]
 ==== <frame-options>
 When enabled adds the http://tools.ietf.org/html/draft-ietf-websec-x-frame-options[X-Frame-Options header] to the response, this allows newer browsers to do some security checks and prevent http://en.wikipedia.org/wiki/Clickjacking[clickjacking] attacks.

+ 132 - 0
web/src/main/java/org/springframework/security/web/header/writers/ContentSecurityPolicyHeaderWriter.java

@@ -0,0 +1,132 @@
+/*
+ * Copyright 2002-2016 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.springframework.security.web.header.writers;
+
+import org.springframework.security.web.header.HeaderWriter;
+import org.springframework.util.Assert;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+/**
+ * <p>
+ * Provides support for <a href="https://www.w3.org/TR/CSP2/">Content Security Policy (CSP) Level 2</a>.
+ * </p>
+ *
+ * <p>
+ * CSP provides a mechanism for web applications to mitigate content injection vulnerabilities,
+ * such as cross-site scripting (XSS). CSP is a declarative policy that allows web application authors to inform
+ * the client (user-agent) about the sources from which the application expects to load resources.
+ * </p>
+ *
+ * <p>
+ * For example, a web application can declare that it only expects to load script from specific, trusted sources.
+ * This declaration allows the client to detect and block malicious scripts injected into the application by an attacker.
+ * </p>
+ *
+ * <p>
+ * A declaration of a security policy contains a set of security policy directives (for example, script-src and object-src),
+ * each responsible for declaring the restrictions for a particular resource type.
+ * The list of directives defined can be found at
+ * <a href="https://www.w3.org/TR/CSP2/#directives">Directives</a>.
+ * </p>
+ *
+ * <p>
+ * Each directive has a name and value. For detailed syntax on writing security policies, see
+ * <a href="https://www.w3.org/TR/CSP2/#syntax-and-algorithms">Syntax and Algorithms</a>.
+ * </p>
+ *
+ * <p>
+ * This implementation of {@link HeaderWriter} writes one of the following headers:
+ * </p>
+ * <ul>
+ * 	<li>Content-Security-Policy</li>
+ * 	<li>Content-Security-Policy-Report-Only</li>
+ * </ul>
+ *
+ * <p>
+ * By default, the Content-Security-Policy header is included in the response.
+ * However, calling {@link #setReportOnly(boolean)} with {@code true} will include the
+ * Content-Security-Policy-Report-Only header in the response.
+ * <strong>NOTE:</strong> The supplied security policy directive(s) will be used for whichever header is enabled (included).
+ * </p>
+ *
+ * <p>
+ * <strong>
+ * CSP is not intended as a first line of defense against content injection vulnerabilities.
+ * Instead, CSP is used to reduce the harm caused by content injection attacks.
+ * As a first line of defense against content injection, web application authors should validate their input and encode their output.
+ * </strong>
+ * </p>
+ *
+ * @author Joe Grandja
+ * @since 4.1
+ */
+public final class ContentSecurityPolicyHeaderWriter implements HeaderWriter {
+	private static final String CONTENT_SECURITY_POLICY_HEADER = "Content-Security-Policy";
+
+	private static final String CONTENT_SECURITY_POLICY_REPORT_ONLY_HEADER = "Content-Security-Policy-Report-Only";
+
+	private String policyDirectives;
+
+	private boolean reportOnly;
+
+	/**
+	 * Creates a new instance
+	 *
+	 * @param policyDirectives maps to {@link #setPolicyDirectives(String)}
+	 * @throws IllegalArgumentException if policyDirectives is null or empty
+	 */
+	public ContentSecurityPolicyHeaderWriter(String policyDirectives) {
+		setPolicyDirectives(policyDirectives);
+		this.reportOnly = false;
+	}
+
+	/**
+	 * @see org.springframework.security.web.header.HeaderWriter#writeHeaders(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
+	 */
+	@Override
+	public void writeHeaders(HttpServletRequest request, HttpServletResponse response) {
+		response.setHeader((!reportOnly ? CONTENT_SECURITY_POLICY_HEADER : CONTENT_SECURITY_POLICY_REPORT_ONLY_HEADER), policyDirectives);
+	}
+
+	/**
+	 * Sets the security policy directive(s) to be used in the response header.
+	 *
+	 * @param policyDirectives the security policy directive(s)
+	 * @throws IllegalArgumentException if policyDirectives is null or empty
+	 */
+	public void setPolicyDirectives(String policyDirectives) {
+		Assert.hasLength(policyDirectives, "policyDirectives cannot be null or empty");
+		this.policyDirectives = policyDirectives;
+	}
+
+	/**
+	 * If true, includes the Content-Security-Policy-Report-Only header in the response,
+	 * otherwise, defaults to the Content-Security-Policy header.
+
+	 * @param reportOnly set to true for reporting policy violations only
+	 */
+	public void setReportOnly(boolean reportOnly) {
+		this.reportOnly = reportOnly;
+	}
+
+	@Override
+	public String toString() {
+		return getClass().getName() + " [policyDirectives=" + policyDirectives + "; reportOnly=" + reportOnly + "]";
+	}
+
+}

+ 90 - 0
web/src/test/java/org/springframework/security/web/header/writers/ContentSecurityPolicyHeaderWriterTests.java

@@ -0,0 +1,90 @@
+/*
+ * Copyright 2002-2016 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.springframework.security.web.header.writers;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.springframework.mock.web.MockHttpServletRequest;
+import org.springframework.mock.web.MockHttpServletResponse;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+/**
+ * @author Joe Grandja
+ */
+public class ContentSecurityPolicyHeaderWriterTests {
+	private static final String DEFAULT_POLICY_DIRECTIVES = "default-src 'self'";
+	private MockHttpServletRequest request;
+	private MockHttpServletResponse response;
+	private ContentSecurityPolicyHeaderWriter writer;
+
+	@Before
+	public void setup() {
+		request = new MockHttpServletRequest();
+		request.setSecure(true);
+		response = new MockHttpServletResponse();
+		writer = new ContentSecurityPolicyHeaderWriter(DEFAULT_POLICY_DIRECTIVES);
+	}
+
+	@Test
+	public void writeHeadersContentSecurityPolicyDefault() {
+		writer.writeHeaders(request, response);
+
+		assertThat(response.getHeaderNames().size()).isEqualTo(1);
+		assertThat(response.getHeader("Content-Security-Policy")).isEqualTo(DEFAULT_POLICY_DIRECTIVES);
+	}
+
+	@Test
+	public void writeHeadersContentSecurityPolicyCustom() {
+		String policyDirectives = "default-src 'self'; " +
+				"object-src plugins1.example.com plugins2.example.com; " +
+				"script-src trustedscripts.example.com";
+
+		writer = new ContentSecurityPolicyHeaderWriter(policyDirectives);
+		writer.writeHeaders(request, response);
+
+		assertThat(response.getHeaderNames().size()).isEqualTo(1);
+		assertThat(response.getHeader("Content-Security-Policy")).isEqualTo(policyDirectives);
+	}
+
+	@Test
+	public void writeHeadersContentSecurityPolicyReportOnlyDefault() {
+		writer.setReportOnly(true);
+		writer.writeHeaders(request, response);
+
+		assertThat(response.getHeaderNames().size()).isEqualTo(1);
+		assertThat(response.getHeader("Content-Security-Policy-Report-Only")).isEqualTo(DEFAULT_POLICY_DIRECTIVES);
+	}
+
+	@Test
+	public void writeHeadersContentSecurityPolicyReportOnlyCustom() {
+		String policyDirectives = "default-src https:; report-uri https://example.com/";
+
+		writer = new ContentSecurityPolicyHeaderWriter(policyDirectives);
+		writer.setReportOnly(true);
+		writer.writeHeaders(request, response);
+
+		assertThat(response.getHeaderNames().size()).isEqualTo(1);
+		assertThat(response.getHeader("Content-Security-Policy-Report-Only")).isEqualTo(policyDirectives);
+	}
+
+	@Test(expected = IllegalArgumentException.class)
+	public void writeHeadersContentSecurityPolicyInvalid() {
+		writer = new ContentSecurityPolicyHeaderWriter("");
+		writer = new ContentSecurityPolicyHeaderWriter(null);
+	}
+
+}