Add support for Feature-Policy security header

This commit is contained in:
Vedran Pavic 2018-08-15 22:05:10 +02:00 committed by Rob Winch
parent 9c478257d4
commit c6ea447cc0
9 changed files with 368 additions and 5 deletions

View File

@ -1,5 +1,5 @@
/*
* Copyright 2002-2016 the original author or authors.
* Copyright 2002-2018 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.
@ -13,6 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.config.annotation.web.configurers;
import java.net.URI;
@ -58,6 +59,7 @@ import org.springframework.util.Assert;
* @author Tim Ysewyn
* @author Joe Grandja
* @author Eddú Meléndez
* @author Vedran Pavic
* @since 3.2
*/
public class HeadersConfigurer<H extends HttpSecurityBuilder<H>> extends
@ -82,6 +84,8 @@ public class HeadersConfigurer<H extends HttpSecurityBuilder<H>> extends
private final ReferrerPolicyConfig referrerPolicy = new ReferrerPolicyConfig();
private final FeaturePolicyConfig featurePolicy = new FeaturePolicyConfig();
/**
* Creates a new instance
*
@ -775,6 +779,7 @@ public class HeadersConfigurer<H extends HttpSecurityBuilder<H>> extends
addIfNotNull(writers, hpkp.writer);
addIfNotNull(writers, contentSecurityPolicy.writer);
addIfNotNull(writers, referrerPolicy.writer);
addIfNotNull(writers, featurePolicy.writer);
writers.addAll(headerWriters);
return writers;
}
@ -848,4 +853,44 @@ public class HeadersConfigurer<H extends HttpSecurityBuilder<H>> extends
}
}
/**
* Allows configuration for <a href="https://wicg.github.io/feature-policy/">Feature
* Policy</a>.
* <p>
* Calling this method automatically enables (includes) the {@code Feature-Policy}
* header in the response using the supplied policy directive(s).
* <p>
* Configuration is provided to the {@link FeaturePolicyHeaderWriter} which is
* responsible for writing the header.
*
* @see FeaturePolicyHeaderWriter
* @since 5.1
* @return the {@link FeaturePolicyHeaderWriter} for additional configuration
* @throws IllegalArgumentException if policyDirectives is {@code null} or empty
*/
public FeaturePolicyConfig featurePolicy(String policyDirectives) {
this.featurePolicy.writer = new FeaturePolicyHeaderWriter(policyDirectives);
return featurePolicy;
}
public final class FeaturePolicyConfig {
private FeaturePolicyHeaderWriter writer;
private FeaturePolicyConfig() {
}
/**
* Allows completing configuration of Feature Policy and continuing configuration
* of headers.
*
* @return the {@link HeadersConfigurer} for additional configuration
*/
public HeadersConfigurer<H> and() {
return HeadersConfigurer.this;
}
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2002-2016 the original author or authors.
* Copyright 2002-2018 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.
@ -13,6 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.config.http;
import java.net.URI;
@ -47,6 +48,7 @@ import org.w3c.dom.Node;
* @author Marten Deinum
* @author Tim Ysewyn
* @author Eddú Meléndez
* @author Vedran Pavic
* @since 3.2
*/
public class HeadersBeanDefinitionParser implements BeanDefinitionParser {
@ -85,6 +87,7 @@ public class HeadersBeanDefinitionParser implements BeanDefinitionParser {
private static final String CONTENT_SECURITY_POLICY_ELEMENT = "content-security-policy";
private static final String REFERRER_POLICY_ELEMENT = "referrer-policy";
private static final String FEATURE_POLICY_ELEMENT = "feature-policy";
private static final String ALLOW_FROM = "ALLOW-FROM";
@ -114,6 +117,8 @@ public class HeadersBeanDefinitionParser implements BeanDefinitionParser {
parseReferrerPolicyElement(element, parserContext);
parseFeaturePolicyElement(element, parserContext);
parseHeaderElements(element);
boolean noWriters = headerWriters.isEmpty();
@ -313,6 +318,32 @@ public class HeadersBeanDefinitionParser implements BeanDefinitionParser {
headerWriters.add(headersWriter.getBeanDefinition());
}
private void parseFeaturePolicyElement(Element element, ParserContext context) {
Element featurePolicyElement = (element == null) ? null
: DomUtils.getChildElementByTagName(element, FEATURE_POLICY_ELEMENT);
if (featurePolicyElement != null) {
addFeaturePolicy(featurePolicyElement, context);
}
}
private void addFeaturePolicy(Element featurePolicyElement, ParserContext context) {
BeanDefinitionBuilder headersWriter = BeanDefinitionBuilder
.genericBeanDefinition(FeaturePolicyHeaderWriter.class);
String policyDirectives = featurePolicyElement
.getAttribute(ATT_POLICY_DIRECTIVES);
if (!StringUtils.hasText(policyDirectives)) {
context.getReaderContext().error(
ATT_POLICY_DIRECTIVES + " requires a 'value' to be set.",
featurePolicyElement);
}
else {
headersWriter.addConstructorArgValue(policyDirectives);
}
headerWriters.add(headersWriter.getBeanDefinition());
}
private void attrNotAllowed(ParserContext context, String attrName,
String otherAttrName, Element element) {
context.getReaderContext().error(

View File

@ -743,7 +743,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? & content-security-policy? & referrer-policy? & header*)}
element headers { headers-options.attlist, (cache-control? & xss-protection? & hsts? & frame-options? & content-type-options? & hpkp? & content-security-policy? & referrer-policy? & feature-policy? & header*)}
headers-options.attlist &=
## Specifies if the default headers should be disabled. Default false.
attribute defaults-disabled {xsd:boolean}?
@ -821,6 +821,13 @@ referrer-options.attlist &=
## The policies for the Referrer-Policy header.
attribute policy {"no-referrer","no-referrer-when-downgrade","same-origin","origin","strict-origin","origin-when-cross-origin","strict-origin-when-cross-origin","unsafe-url"}?
feature-policy =
## Adds support for Feature Policy
element feature-policy {feature-options.attlist}
feature-options.attlist &=
## The security policy directive(s) for the Feature-Policy header.
attribute policy-directives {xsd:token}?
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}

View File

@ -2253,6 +2253,7 @@
<xs:element ref="security:hpkp"/>
<xs:element ref="security:content-security-policy"/>
<xs:element ref="security:referrer-policy"/>
<xs:element ref="security:feature-policy"/>
<xs:element ref="security:header"/>
</xs:choice>
<xs:attributeGroup ref="security:headers-options.attlist"/>
@ -2464,6 +2465,21 @@
</xs:simpleType>
</xs:attribute>
</xs:attributeGroup>
<xs:element name="feature-policy">
<xs:annotation>
<xs:documentation>Adds support for Feature Policy</xs:documentation>
</xs:annotation>
<xs:complexType>
<xs:attributeGroup ref="security:feature-options.attlist"/>
</xs:complexType>
</xs:element>
<xs:attributeGroup name="feature-options.attlist">
<xs:attribute name="policy-directives" type="xs:token">
<xs:annotation>
<xs:documentation>The security policy directive(s) for the Feature-Policy header.</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
@ -2719,4 +2735,4 @@
<xs:enumeration value="LAST"/>
</xs:restriction>
</xs:simpleType>
</xs:schema>
</xs:schema>

View File

@ -1,5 +1,5 @@
/*
* Copyright 2002-2016 the original author or authors.
* Copyright 2002-2018 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.
@ -13,6 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.config.annotation.web.configurers
import org.springframework.beans.factory.BeanCreationException
@ -20,14 +21,17 @@ 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
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter
import static org.springframework.security.web.header.writers.ReferrerPolicyHeaderWriter.ReferrerPolicy
/**
* Tests for {@link HeadersConfigurer}.
*
* @author Rob Winch
* @author Tim Ysewyn
* @author Joe Grandja
* @author Eddú Meléndez
* @author Vedran Pavic
*/
class HeadersConfigurerTests extends BaseSpringSpec {
@ -497,4 +501,45 @@ class HeadersConfigurerTests extends BaseSpringSpec {
}
}
def "headers.featurePolicy default header"() {
setup:
loadConfig(FeaturePolicyDefaultConfig)
request.secure = true
when:
springSecurityFilterChain.doFilter(request, response, chain)
then:
responseHeaders == ['Feature-Policy': 'geolocation \'self\'']
}
@EnableWebSecurity
static class FeaturePolicyDefaultConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.headers()
.defaultsDisabled()
.featurePolicy("geolocation 'self'");
}
}
def "headers.featurePolicy empty policyDirectives"() {
when:
loadConfig(FeaturePolicyInvalidConfig)
then:
thrown(BeanCreationException)
}
@EnableWebSecurity
static class FeaturePolicyInvalidConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.headers()
.defaultsDisabled()
.featurePolicy("");
}
}
}

View File

@ -241,6 +241,7 @@ This allows HTTPS websites to resist impersonation by attackers using mis-issued
** `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).
** `Referrer-Policy` - Can be set using the <<nsa-referrer-policy,referrer-policy>> element, https://www.w3.org/TR/referrer-policy/[Referrer-Policy] is a mechanism that web applications can leverage to manage the referrer field, which contains the last page the user was on.
** `Feature-Policy` - Can be set using the <<nsa-feature-policy,feature-policy>> element, https://wicg.github.io/feature-policy/[Feature-Policy] is a mechanism that allows web developers to selectively enable, disable, and modify the behavior of certain APIs and web features in the browser.
[[nsa-headers-attributes]]
===== <headers> Attributes
@ -272,6 +273,7 @@ The default is false (the headers are enabled).
* <<nsa-cache-control,cache-control>>
* <<nsa-content-security-policy,content-security-policy>>
* <<nsa-content-type-options,content-type-options>>
* <<nsa-feature-policy,feature-policy>>
* <<nsa-frame-options,frame-options>>
* <<nsa-header,header>>
* <<nsa-hpkp,hpkp>>
@ -459,6 +461,24 @@ Default "no-referrer".
[[nsa-feature-policy]]
==== <feature-policy>
When enabled adds the https://wicg.github.io/feature-policy/[Feature Policy] header to the response.
[[nsa-feature-policy-attributes]]
===== <feature-policy> Attributes
[[nsa-feature-policy-policy-directives]]
* **policy-directives**
The security policy directive(s) for the Feature-Policy header.
[[nsa-feature-policy-parents]]
===== Parent Elements of <feature-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.

View File

@ -714,6 +714,56 @@ protected void configure(HttpSecurity http) throws Exception {
----
[[headers-feature]]
==== Feature Policy
https://wicg.github.io/feature-policy/[Feature Policy] is a mechanism that allows web developers to selectively enable, disable, and modify the behavior of certain APIs and web features in the browser.
[source]
----
Feature-Policy: geolocation 'self'
----
With Feature Policy, developers can opt-in to a set of "policies" for the browser to enforce on specific features used throughout your site.
These policies restrict what APIs the site can access or modify the browser's default behavior for certain features.
[[headers-feature-configure]]
===== Configuring Feature Policy
Spring Security *_doesn't add_* Feature Policy header by default.
You can enable the Feature-Policy header using XML configuration with the <<nsa-feature-policy,<feature-policy>>> element as shown below:
[source,xml]
----
<http>
<!-- ... -->
<headers>
<feature-policy policy-directives="geolocation 'self'" />
</headers>
</http>
----
Similarly, you can enable the Feature Policy 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()
.featurePolicy("geolocation 'self'");
}
}
----
[[headers-custom]]
=== Custom Headers
Spring Security has mechanisms to make it convenient to add the more common security headers to your application.

View File

@ -0,0 +1,76 @@
/*
* Copyright 2002-2018 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 javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.security.web.header.HeaderWriter;
import org.springframework.util.Assert;
/**
* Provides support for <a href="https://wicg.github.io/feature-policy/">Feature
* Policy</a>.
* <p>
* Feature Policy allows web developers to selectively enable, disable, and modify the
* behavior of certain APIs and web features in the browser.
* <p>
* A declaration of a feature policy contains a set of security policy directives, each
* responsible for declaring the restrictions for a particular feature type.
*
* @author Vedran Pavic
* @since 5.1
*/
public final class FeaturePolicyHeaderWriter implements HeaderWriter {
private static final String FEATURE_POLICY_HEADER = "Feature-Policy";
private String policyDirectives;
/**
* Create a new instance of {@link FeaturePolicyHeaderWriter} with supplied security
* policy directive(s).
*
* @param policyDirectives the security policy directive(s)
* @throws IllegalArgumentException if policyDirectives is {@code null} or empty
*/
public FeaturePolicyHeaderWriter(String policyDirectives) {
setPolicyDirectives(policyDirectives);
}
@Override
public void writeHeaders(HttpServletRequest request, HttpServletResponse response) {
response.setHeader(FEATURE_POLICY_HEADER, this.policyDirectives);
}
/**
* Set the security policy directive(s) to be used in the response header.
*
* @param policyDirectives the security policy directive(s)
* @throws IllegalArgumentException if policyDirectives is {@code null} or empty
*/
public void setPolicyDirectives(String policyDirectives) {
Assert.hasLength(policyDirectives, "policyDirectives must not be null or empty");
this.policyDirectives = policyDirectives;
}
@Override
public String toString() {
return getClass().getName() + " [policyDirectives=" + this.policyDirectives + "]";
}
}

View File

@ -0,0 +1,73 @@
/*
* Copyright 2002-2018 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;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
/**
* Tests for {@link FeaturePolicyHeaderWriter}.
*
* @author Vedran Pavic
*/
public class FeaturePolicyHeaderWriterTests {
private static final String DEFAULT_POLICY_DIRECTIVES = "geolocation 'self'";
private MockHttpServletRequest request;
private MockHttpServletResponse response;
private FeaturePolicyHeaderWriter writer;
@Before
public void setUp() {
this.request = new MockHttpServletRequest();
this.response = new MockHttpServletResponse();
this.writer = new FeaturePolicyHeaderWriter(DEFAULT_POLICY_DIRECTIVES);
}
@Test
public void writeHeadersFeaturePolicyDefault() {
writer.writeHeaders(this.request, this.response);
assertThat(this.response.getHeaderNames()).hasSize(1);
assertThat(this.response.getHeader("Feature-Policy"))
.isEqualTo(DEFAULT_POLICY_DIRECTIVES);
}
@Test
public void createWriterWithNullDirectivesShouldThrowException() {
assertThatThrownBy(() -> new FeaturePolicyHeaderWriter(null))
.isInstanceOf(IllegalArgumentException.class)
.hasMessage("policyDirectives must not be null or empty");
}
@Test
public void createWriterWithEmptyDirectivesShouldThrowException() {
assertThatThrownBy(() -> new FeaturePolicyHeaderWriter(""))
.isInstanceOf(IllegalArgumentException.class)
.hasMessage("policyDirectives must not be null or empty");
}
}