1
0
mirror of synced 2026-08-05 01:36:56 +00:00

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