Adds support for Content Security Policy

Fixes gh-2342
This commit is contained in:
Joe Grandja 2016-03-17 10:07:40 -04:00 committed by Rob Winch
parent 4cb9b202f8
commit 2f7f2ff589
9 changed files with 675 additions and 17 deletions

View File

@ -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.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.web.header.HeaderWriter; import org.springframework.security.web.header.HeaderWriter;
import org.springframework.security.web.header.HeaderWriterFilter; import org.springframework.security.web.header.HeaderWriterFilter;
import org.springframework.security.web.header.writers.CacheControlHeadersWriter; import org.springframework.security.web.header.writers.*;
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.frameoptions.XFrameOptionsHeaderWriter; import org.springframework.security.web.header.writers.frameoptions.XFrameOptionsHeaderWriter;
import org.springframework.security.web.header.writers.frameoptions.XFrameOptionsHeaderWriter.XFrameOptionsMode; import org.springframework.security.web.header.writers.frameoptions.XFrameOptionsHeaderWriter.XFrameOptionsMode;
import org.springframework.security.web.util.matcher.RequestMatcher; import org.springframework.security.web.util.matcher.RequestMatcher;
@ -59,6 +55,7 @@ import org.springframework.util.Assert;
* *
* @author Rob Winch * @author Rob Winch
* @author Tim Ysewyn * @author Tim Ysewyn
* @author Joe Grandja
* @since 3.2 * @since 3.2
*/ */
public class HeadersConfigurer<H extends HttpSecurityBuilder<H>> extends 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 HpkpConfig hpkp = new HpkpConfig();
private final ContentSecurityPolicyConfig contentSecurityPolicy = new ContentSecurityPolicyConfig();
/** /**
* Creates a new instance * 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 * 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 * 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, hsts.writer);
addIfNotNull(writers, frameOptions.writer); addIfNotNull(writers, frameOptions.writer);
addIfNotNull(writers, hpkp.writer); addIfNotNull(writers, hpkp.writer);
addIfNotNull(writers, contentSecurityPolicy.writer);
writers.addAll(headerWriters); writers.addAll(headerWriters);
return writers; return writers;
} }

View File

@ -67,6 +67,7 @@ public class HeadersBeanDefinitionParser implements BeanDefinitionParser {
private static final String ATT_REPORT_ONLY = "report-only"; private static final String ATT_REPORT_ONLY = "report-only";
private static final String ATT_REPORT_URI = "report-uri"; private static final String ATT_REPORT_URI = "report-uri";
private static final String ATT_ALGORITHM = "algorithm"; 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"; 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 FRAME_OPTIONS_ELEMENT = "frame-options";
private static final String GENERIC_HEADER_ELEMENT = "header"; 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 static final String ALLOW_FROM = "ALLOW-FROM";
private ManagedList<BeanMetadataElement> headerWriters; private ManagedList<BeanMetadataElement> headerWriters;
@ -104,6 +107,8 @@ public class HeadersBeanDefinitionParser implements BeanDefinitionParser {
parseHpkpElement(element == null || !disabled, element, parserContext); parseHpkpElement(element == null || !disabled, element, parserContext);
parseContentSecurityPolicyElement(disabled, element, parserContext);
parseHeaderElements(element); parseHeaderElements(element);
if (disabled) { 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, private void attrNotAllowed(ParserContext context, String attrName,
String otherAttrName, Element element) { String otherAttrName, Element element) {
context.getReaderContext().error( context.getReaderContext().error(

View File

@ -748,7 +748,7 @@ csrf-options.attlist &=
headers = 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 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 &= headers-options.attlist &=
## Specifies if the default headers should be disabled. Default false. ## Specifies if the default headers should be disabled. Default false.
attribute defaults-disabled {xsd:boolean}? attribute defaults-disabled {xsd:boolean}?
@ -800,6 +800,16 @@ hpkp.attlist &=
## Specifies the URI to which the browser should report pin validation failures. ## Specifies the URI to which the browser should report pin validation failures.
attribute report-uri {xsd:string}? 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 = cache-control =
## Adds Cache-Control no-cache, no-store, must-revalidate, Pragma no-cache, and Expires 0 for every request ## Adds Cache-Control no-cache, no-store, must-revalidate, Pragma no-cache, and Expires 0 for every request
element cache-control {cache-control.attlist} element cache-control {cache-control.attlist}

View File

@ -2328,6 +2328,7 @@
<xs:element ref="security:frame-options"/> <xs:element ref="security:frame-options"/>
<xs:element ref="security:content-type-options"/> <xs:element ref="security:content-type-options"/>
<xs:element ref="security:hpkp"/> <xs:element ref="security:hpkp"/>
<xs:element ref="security:content-security-policy"/>
<xs:element ref="security:header"/> <xs:element ref="security:header"/>
</xs:choice> </xs:choice>
<xs:attributeGroup ref="security:headers-options.attlist"/> <xs:attributeGroup ref="security:headers-options.attlist"/>
@ -2460,6 +2461,31 @@
</xs:annotation> </xs:annotation>
</xs:attribute> </xs:attribute>
</xs:attributeGroup> </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:element name="cache-control">
<xs:annotation> <xs:annotation>
<xs:documentation>Adds Cache-Control no-cache, no-store, must-revalidate, Pragma no-cache, and Expires 0 for <xs:documentation>Adds Cache-Control no-cache, no-store, must-revalidate, Pragma no-cache, and Expires 0 for

View File

@ -15,6 +15,7 @@
*/ */
package org.springframework.security.config.annotation.web.configurers 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.BaseSpringSpec
import org.springframework.security.config.annotation.web.builders.HttpSecurity import org.springframework.security.config.annotation.web.builders.HttpSecurity
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity 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 Rob Winch
* @author Tim Ysewyn * @author Tim Ysewyn
* @author Joe Grandja
*/ */
class HeadersConfigurerTests extends BaseSpringSpec { class HeadersConfigurerTests extends BaseSpringSpec {
@ -387,4 +389,68 @@ class HeadersConfigurerTests extends BaseSpringSpec {
.reportUri("http://example.net/pkp-report") .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("");
}
}
} }

View File

@ -830,6 +830,84 @@ class HttpHeadersConfigTests extends AbstractHttpConfigTests {
expected.message.contains 'policy' 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) { def assertHeaders(MockHttpServletResponse response, Map<String,String> expected) {
assert response.headerNames == expected.keySet() assert response.headerNames == expected.keySet()
expected.each { headerName, value -> expected.each { headerName, value ->

View File

@ -3825,7 +3825,7 @@ Allowing your website to be added to a frame can be a security issue. For exampl
[NOTE] [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. 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 policys _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]] [[headers-custom]]
=== Custom Headers === 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. 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]] [[headers-static]]
==== Static Headers ==== Static Headers
There may be times you wish to inject custom security headers into your application that are not supported out of the box. 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. For example, given the following custom security header:
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:
[source] [source]
---- ----
X-Content-Security-Policy: default-src 'self' X-Custom-Security-Header: header-value
X-WebKit-CSP: default-src 'self'
---- ----
When using the XML namespace, these headers can be added to the response using the <<nsa-header,<header>>> element as shown below: 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> <headers>
<header name="X-Content-Security-Policy" value="default-src 'self'"/> <header name="X-Custom-Security-Header" value="header-value"/>
<header name="X-WebKit-CSP" value="default-src 'self'"/>
</headers> </headers>
</http> </http>
---- ----
@ -3962,8 +4104,7 @@ protected void configure(HttpSecurity http) throws Exception {
http http
// ... // ...
.headers() .headers()
.addHeaderWriter(new StaticHeadersWriter("X-Content-Security-Policy","default-src 'self'")) .addHeaderWriter(new StaticHeadersWriter("X-Custom-Security-Header","header-value"));
.addHeaderWriter(new StaticHeadersWriter("X-WebKit-CSP","default-src 'self'"));
} }
} }
---- ----
@ -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-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. ** `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. ** `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]] [[nsa-headers-attributes]]
===== <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-cache-control,cache-control>>
* <<nsa-content-security-policy,content-security-policy>>
* <<nsa-content-type-options,content-type-options>> * <<nsa-content-type-options,content-type-options>>
* <<nsa-frame-options,frame-options>> * <<nsa-frame-options,frame-options>>
* <<nsa-header,header>> * <<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]] [[nsa-frame-options]]
==== <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. 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.

View File

@ -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 + "]";
}
}

View File

@ -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);
}
}