parent
4cb9b202f8
commit
2f7f2ff589
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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("");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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 ->
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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 + "]";
|
||||
}
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in New Issue