فهرست منبع

Add permissionsPolicy http header

Christophe Gilles 4 سال پیش
والد
کامیت
54d3839f63
21فایلهای تغییر یافته به همراه1015 افزوده شده و 3 حذف شده
  1. 79 0
      config/src/main/java/org/springframework/security/config/annotation/web/configurers/HeadersConfigurer.java
  2. 25 0
      config/src/main/java/org/springframework/security/config/http/HeadersBeanDefinitionParser.java
  3. 57 2
      config/src/main/java/org/springframework/security/config/web/server/ServerHttpSecurity.java
  4. 19 0
      config/src/main/kotlin/org/springframework/security/config/web/server/ServerHeadersDsl.kt
  5. 40 0
      config/src/main/kotlin/org/springframework/security/config/web/server/ServerPermissionsPolicyDsl.kt
  6. 19 0
      config/src/main/kotlin/org/springframework/security/config/web/servlet/HeadersDsl.kt
  7. 41 0
      config/src/main/kotlin/org/springframework/security/config/web/servlet/headers/PermissionsPolicyDsl.kt
  8. 8 1
      config/src/main/resources/org/springframework/security/config/spring-security-5.5.rnc
  9. 18 0
      config/src/main/resources/org/springframework/security/config/spring-security-5.5.xsd
  10. 100 0
      config/src/test/java/org/springframework/security/config/annotation/web/configurers/HeadersConfigurerTests.java
  11. 11 0
      config/src/test/java/org/springframework/security/config/http/HttpHeadersConfigTests.java
  12. 97 0
      config/src/test/kotlin/org/springframework/security/config/web/server/ServerPermissionsPolicyDslTests.kt
  13. 24 0
      config/src/test/kotlin/org/springframework/security/config/web/servlet/HeadersDslTests.kt
  14. 36 0
      config/src/test/resources/org/springframework/security/config/http/HttpHeadersConfigTests-DefaultsDisabledWithPermissionsPolicy.xml
  15. 22 0
      docs/manual/src/docs/asciidoc/_includes/about/exploits/headers.adoc
  16. 52 0
      docs/manual/src/docs/asciidoc/_includes/reactive/exploits/headers.adoc
  17. 70 0
      docs/manual/src/docs/asciidoc/_includes/servlet/exploits/headers.adoc
  18. 82 0
      web/src/main/java/org/springframework/security/web/header/writers/PermissionsPolicyHeaderWriter.java
  19. 59 0
      web/src/main/java/org/springframework/security/web/server/header/PermissionsPolicyServerHttpHeadersWriter.java
  20. 79 0
      web/src/test/java/org/springframework/security/web/header/writers/PermissionsPolicyHeaderWriterTests.java
  21. 77 0
      web/src/test/java/org/springframework/security/web/server/header/PermissionsPolicyServerHttpHeadersWriterTests.java

+ 79 - 0
config/src/main/java/org/springframework/security/config/annotation/web/configurers/HeadersConfigurer.java

@@ -34,6 +34,7 @@ import org.springframework.security.web.header.writers.ContentSecurityPolicyHead
 import org.springframework.security.web.header.writers.FeaturePolicyHeaderWriter;
 import org.springframework.security.web.header.writers.HpkpHeaderWriter;
 import org.springframework.security.web.header.writers.HstsHeaderWriter;
+import org.springframework.security.web.header.writers.PermissionsPolicyHeaderWriter;
 import org.springframework.security.web.header.writers.ReferrerPolicyHeaderWriter;
 import org.springframework.security.web.header.writers.ReferrerPolicyHeaderWriter.ReferrerPolicy;
 import org.springframework.security.web.header.writers.XContentTypeOptionsHeaderWriter;
@@ -93,6 +94,8 @@ public class HeadersConfigurer<H extends HttpSecurityBuilder<H>>
 
 	private final FeaturePolicyConfig featurePolicy = new FeaturePolicyConfig();
 
+	private final PermissionsPolicyConfig permissionsPolicy = new PermissionsPolicyConfig();
+
 	/**
 	 * Creates a new instance
 	 *
@@ -387,6 +390,7 @@ public class HeadersConfigurer<H extends HttpSecurityBuilder<H>>
 		addIfNotNull(writers, this.contentSecurityPolicy.writer);
 		addIfNotNull(writers, this.referrerPolicy.writer);
 		addIfNotNull(writers, this.featurePolicy.writer);
+		addIfNotNull(writers, this.permissionsPolicy.writer);
 		writers.addAll(this.headerWriters);
 		return writers;
 	}
@@ -487,12 +491,58 @@ public class HeadersConfigurer<H extends HttpSecurityBuilder<H>>
 	 * @throws IllegalArgumentException if policyDirectives is {@code null} or empty
 	 * @since 5.1
 	 * @see FeaturePolicyHeaderWriter
+	 * @deprecated Use {@link #permissionsPolicy(Customizer)} instead.
 	 */
+	@Deprecated
 	public FeaturePolicyConfig featurePolicy(String policyDirectives) {
 		this.featurePolicy.writer = new FeaturePolicyHeaderWriter(policyDirectives);
 		return this.featurePolicy;
 	}
 
+	/**
+	 * <p>
+	 * Allows configuration for
+	 * <a href="https://w3c.github.io/webappsec-permissions-policy/">Permissions
+	 * Policy</a>.
+	 * </p>
+	 *
+	 * <p>
+	 * Configuration is provided to the {@link PermissionsPolicyHeaderWriter} which
+	 * support the writing of the header as detailed in the W3C Technical Report:
+	 * </p>
+	 * <ul>
+	 * <li>Permissions-Policy</li>
+	 * </ul>
+	 * @return the {@link PermissionsPolicyConfig} for additional configuration
+	 * @since 5.5
+	 * @see PermissionsPolicyHeaderWriter
+	 */
+	public PermissionsPolicyConfig permissionsPolicy() {
+		this.permissionsPolicy.writer = new PermissionsPolicyHeaderWriter();
+		return this.permissionsPolicy;
+	}
+
+	/**
+	 * Allows configuration for
+	 * <a href="https://w3c.github.io/webappsec-permissions-policy/"> Permissions
+	 * Policy</a>.
+	 * <p>
+	 * Calling this method automatically enables (includes) the {@code Permissions-Policy}
+	 * header in the response using the supplied policy directive(s).
+	 * <p>
+	 * Configuration is provided to the {@link PermissionsPolicyHeaderWriter} which is
+	 * responsible for writing the header.
+	 * @return the {@link PermissionsPolicyConfig} for additional configuration
+	 * @throws IllegalArgumentException if policyDirectives is {@code null} or empty
+	 * @since 5.5
+	 * @see PermissionsPolicyHeaderWriter
+	 */
+	public PermissionsPolicyConfig permissionsPolicy(Customizer<PermissionsPolicyConfig> permissionsPolicyCustomizer) {
+		this.permissionsPolicy.writer = new PermissionsPolicyHeaderWriter();
+		permissionsPolicyCustomizer.customize(this.permissionsPolicy);
+		return this.permissionsPolicy;
+	}
+
 	public final class ContentTypeOptionsConfig {
 
 		private XContentTypeOptionsHeaderWriter writer;
@@ -1063,4 +1113,33 @@ public class HeadersConfigurer<H extends HttpSecurityBuilder<H>>
 
 	}
 
+	public final class PermissionsPolicyConfig {
+
+		private PermissionsPolicyHeaderWriter writer;
+
+		private PermissionsPolicyConfig() {
+		}
+
+		/**
+		 * Sets the policy to be used in the response header.
+		 * @param policy a permissions policy
+		 * @return the {@link PermissionsPolicyConfig} for additional configuration
+		 * @throws IllegalArgumentException if policy is null
+		 */
+		public PermissionsPolicyConfig policy(String policy) {
+			this.writer.setPolicy(policy);
+			return this;
+		}
+
+		/**
+		 * Allows completing configuration of Permissions Policy and continuing
+		 * configuration of headers.
+		 * @return the {@link HeadersConfigurer} for additional configuration
+		 */
+		public HeadersConfigurer<H> and() {
+			return HeadersConfigurer.this;
+		}
+
+	}
+
 }

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

@@ -39,6 +39,7 @@ import org.springframework.security.web.header.writers.ContentSecurityPolicyHead
 import org.springframework.security.web.header.writers.FeaturePolicyHeaderWriter;
 import org.springframework.security.web.header.writers.HpkpHeaderWriter;
 import org.springframework.security.web.header.writers.HstsHeaderWriter;
+import org.springframework.security.web.header.writers.PermissionsPolicyHeaderWriter;
 import org.springframework.security.web.header.writers.ReferrerPolicyHeaderWriter;
 import org.springframework.security.web.header.writers.ReferrerPolicyHeaderWriter.ReferrerPolicy;
 import org.springframework.security.web.header.writers.StaticHeadersWriter;
@@ -119,6 +120,8 @@ public class HeadersBeanDefinitionParser implements BeanDefinitionParser {
 
 	private static final String FEATURE_POLICY_ELEMENT = "feature-policy";
 
+	private static final String PERMISSIONS_POLICY_ELEMENT = "permissions-policy";
+
 	private static final String ALLOW_FROM = "ALLOW-FROM";
 
 	private ManagedList<BeanMetadataElement> headerWriters;
@@ -140,6 +143,7 @@ public class HeadersBeanDefinitionParser implements BeanDefinitionParser {
 		parseContentSecurityPolicyElement(disabled, element, parserContext);
 		parseReferrerPolicyElement(element, parserContext);
 		parseFeaturePolicyElement(element, parserContext);
+		parsePermissionsPolicyElement(element, parserContext);
 		parseHeaderElements(element);
 		boolean noWriters = this.headerWriters.isEmpty();
 		if (disabled && !noWriters) {
@@ -351,6 +355,27 @@ public class HeadersBeanDefinitionParser implements BeanDefinitionParser {
 		this.headerWriters.add(headersWriter.getBeanDefinition());
 	}
 
+	private void parsePermissionsPolicyElement(Element element, ParserContext context) {
+		Element permissionsPolicyElement = (element != null)
+				? DomUtils.getChildElementByTagName(element, PERMISSIONS_POLICY_ELEMENT) : null;
+		if (permissionsPolicyElement != null) {
+			addPermissionsPolicy(permissionsPolicyElement, context);
+		}
+	}
+
+	private void addPermissionsPolicy(Element permissionsPolicyElement, ParserContext context) {
+		BeanDefinitionBuilder headersWriter = BeanDefinitionBuilder
+				.genericBeanDefinition(PermissionsPolicyHeaderWriter.class);
+		String policyDirectives = permissionsPolicyElement.getAttribute(ATT_POLICY);
+		if (!StringUtils.hasText(policyDirectives)) {
+			context.getReaderContext().error(ATT_POLICY + " requires a 'value' to be set.", permissionsPolicyElement);
+		}
+		else {
+			headersWriter.addConstructorArgValue(policyDirectives);
+		}
+		this.headerWriters.add(headersWriter.getBeanDefinition());
+	}
+
 	private void attrNotAllowed(ParserContext context, String attrName, String otherAttrName, Element element) {
 		context.getReaderContext().error("Only one of '" + attrName + "' or '" + otherAttrName + "' can be set.",
 				element);

+ 57 - 2
config/src/main/java/org/springframework/security/config/web/server/ServerHttpSecurity.java

@@ -149,6 +149,7 @@ import org.springframework.security.web.server.header.ContentSecurityPolicyServe
 import org.springframework.security.web.server.header.ContentTypeOptionsServerHttpHeadersWriter;
 import org.springframework.security.web.server.header.FeaturePolicyServerHttpHeadersWriter;
 import org.springframework.security.web.server.header.HttpHeaderWriterWebFilter;
+import org.springframework.security.web.server.header.PermissionsPolicyServerHttpHeadersWriter;
 import org.springframework.security.web.server.header.ReferrerPolicyServerHttpHeadersWriter;
 import org.springframework.security.web.server.header.ReferrerPolicyServerHttpHeadersWriter.ReferrerPolicy;
 import org.springframework.security.web.server.header.ServerHttpHeadersWriter;
@@ -2232,13 +2233,16 @@ public class ServerHttpSecurity {
 
 		private FeaturePolicyServerHttpHeadersWriter featurePolicy = new FeaturePolicyServerHttpHeadersWriter();
 
+		private PermissionsPolicyServerHttpHeadersWriter permissionsPolicy = new PermissionsPolicyServerHttpHeadersWriter();
+
 		private ContentSecurityPolicyServerHttpHeadersWriter contentSecurityPolicy = new ContentSecurityPolicyServerHttpHeadersWriter();
 
 		private ReferrerPolicyServerHttpHeadersWriter referrerPolicy = new ReferrerPolicyServerHttpHeadersWriter();
 
 		private HeaderSpec() {
 			this.writers = new ArrayList<>(Arrays.asList(this.cacheControl, this.contentTypeOptions, this.hsts,
-					this.frameOptions, this.xss, this.featurePolicy, this.contentSecurityPolicy, this.referrerPolicy));
+					this.frameOptions, this.xss, this.featurePolicy, this.permissionsPolicy, this.contentSecurityPolicy,
+					this.referrerPolicy));
 		}
 
 		/**
@@ -2395,13 +2399,32 @@ public class ServerHttpSecurity {
 
 		/**
 		 * Configures {@code Feature-Policy} response header.
-		 * @param policyDirectives the policy directive(s)
+		 * @param policyDirectives the policy
 		 * @return the {@link FeaturePolicySpec} to configure
 		 */
 		public FeaturePolicySpec featurePolicy(String policyDirectives) {
 			return new FeaturePolicySpec(policyDirectives);
 		}
 
+		/**
+		 * Configures {@code Permissions-Policy} response header.
+		 * @return the {@link PermissionsPolicySpec} to configure
+		 */
+		public PermissionsPolicySpec permissionsPolicy() {
+			return new PermissionsPolicySpec();
+		}
+
+		/**
+		 * Configures {@code Permissions-Policy} response header.
+		 * @param permissionsPolicyCustomizer the {@link Customizer} to provide more
+		 * options for the {@link PermissionsPolicySpec}
+		 * @return the {@link HeaderSpec} to customize
+		 */
+		public HeaderSpec permissionsPolicy(Customizer<PermissionsPolicySpec> permissionsPolicyCustomizer) {
+			permissionsPolicyCustomizer.customize(new PermissionsPolicySpec());
+			return this;
+		}
+
 		/**
 		 * Configures {@code Referrer-Policy} response header.
 		 * @param referrerPolicy the policy to use
@@ -2677,6 +2700,38 @@ public class ServerHttpSecurity {
 
 		}
 
+		/**
+		 * Configures {@code Permissions-Policy} response header.
+		 *
+		 * @since 5.5
+		 * @see #permissionsPolicy()
+		 */
+		public final class PermissionsPolicySpec {
+
+			private PermissionsPolicySpec() {
+			}
+
+			/**
+			 * Sets the policy to be used in the response header.
+			 * @param policy a permissions policy
+			 * @return the {@link PermissionsPolicySpec} to continue configuring
+			 */
+			public PermissionsPolicySpec policy(String policy) {
+				HeaderSpec.this.permissionsPolicy.setPolicy(policy);
+				return this;
+			}
+
+			/**
+			 * Allows method chaining to continue configuring the
+			 * {@link ServerHttpSecurity}.
+			 * @return the {@link HeaderSpec} to continue configuring
+			 */
+			public HeaderSpec and() {
+				return HeaderSpec.this;
+			}
+
+		}
+
 		/**
 		 * Configures {@code Referrer-Policy} response header.
 		 *

+ 19 - 0
config/src/main/kotlin/org/springframework/security/config/web/server/ServerHeadersDsl.kt

@@ -34,6 +34,7 @@ class ServerHeadersDsl {
     private var contentSecurityPolicy: ((ServerHttpSecurity.HeaderSpec.ContentSecurityPolicySpec) -> Unit)? = null
     private var referrerPolicy: ((ServerHttpSecurity.HeaderSpec.ReferrerPolicySpec) -> Unit)? = null
     private var featurePolicyDirectives: String? = null
+    private var permissionsPolicy: ((ServerHttpSecurity.HeaderSpec.PermissionsPolicySpec) -> Unit)? = null
 
     private var disabled = false
 
@@ -140,6 +141,21 @@ class ServerHeadersDsl {
         this.featurePolicyDirectives = policyDirectives
     }
 
+    /**
+     * Allows configuration for <a href="https://w3c.github.io/webappsec-permissions-policy/">Permissions
+     * Policy</a>.
+     *
+     * <p>
+     * Calling this method automatically enables (includes) the Permissions-Policy
+     * header in the response using the supplied policy directive(s).
+     * <p>
+     *
+     * @param permissionsPolicyConfig the customization to apply to the header
+     */
+    fun permissionsPolicy(permissionsPolicyConfig: ServerPermissionsPolicyDsl.() -> Unit) {
+        this.permissionsPolicy = ServerPermissionsPolicyDsl().apply(permissionsPolicyConfig).get()
+    }
+
     /**
      * Disables HTTP response headers.
      */
@@ -170,6 +186,9 @@ class ServerHeadersDsl {
             featurePolicyDirectives?.also {
                 headers.featurePolicy(featurePolicyDirectives)
             }
+            permissionsPolicy?.also {
+                headers.permissionsPolicy(permissionsPolicy)
+            }
             referrerPolicy?.also {
                 headers.referrerPolicy(referrerPolicy)
             }

+ 40 - 0
config/src/main/kotlin/org/springframework/security/config/web/server/ServerPermissionsPolicyDsl.kt

@@ -0,0 +1,40 @@
+/*
+ * Copyright 2002-2020 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
+ *
+ *      https://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.config.web.server
+
+import org.springframework.security.web.server.header.ReferrerPolicyServerHttpHeadersWriter
+
+/**
+ * A Kotlin DSL to configure the [ServerHttpSecurity] permissions policy header using
+ * idiomatic Kotlin code.
+ *
+ * @author Christophe Gilles
+ * @since 5.5
+ * @property policy the policy to be used in the response header.
+ */
+@ServerSecurityMarker
+class ServerPermissionsPolicyDsl {
+    var policy: String? = null
+
+    internal fun get(): (ServerHttpSecurity.HeaderSpec.PermissionsPolicySpec) -> Unit {
+        return { permissionsPolicy ->
+            policy?.also {
+                permissionsPolicy.policy(policy)
+            }
+        }
+    }
+}

+ 19 - 0
config/src/main/kotlin/org/springframework/security/config/web/servlet/HeadersDsl.kt

@@ -41,6 +41,7 @@ class HeadersDsl {
     private var contentSecurityPolicy: ((HeadersConfigurer<HttpSecurity>.ContentSecurityPolicyConfig) -> Unit)? = null
     private var referrerPolicy: ((HeadersConfigurer<HttpSecurity>.ReferrerPolicyConfig) -> Unit)? = null
     private var featurePolicyDirectives: String? = null
+    private var permissionsPolicy: ((HeadersConfigurer<HttpSecurity>.PermissionsPolicyConfig) -> Unit)? = null
     private var disabled = false
     private var headerWriters = mutableListOf<HeaderWriter>()
 
@@ -164,6 +165,21 @@ class HeadersDsl {
         this.featurePolicyDirectives = policyDirectives
     }
 
+    /**
+     * Allows configuration for <a href="https://w3c.github.io/webappsec-permissions-policy/">Permissions
+     * Policy</a>.
+     *
+     * <p>
+     * Calling this method automatically enables (includes) the Permissions-Policy
+     * header in the response using the supplied policy directive(s).
+     * <p>
+     *
+     * @param policyDirectives policyDirectives the security policy directive(s)
+     */
+    fun permissionsPolicy(permissionsPolicyConfig: PermissionsPolicyDsl.() -> Unit) {
+        this.permissionsPolicy = PermissionsPolicyDsl().apply(permissionsPolicyConfig).get()
+    }
+
     /**
      * Adds a [HeaderWriter] instance.
      *
@@ -217,6 +233,9 @@ class HeadersDsl {
             featurePolicyDirectives?.also {
                 headers.featurePolicy(featurePolicyDirectives)
             }
+            permissionsPolicy?.also {
+                headers.permissionsPolicy(permissionsPolicy)
+            }
             headerWriters.forEach { headerWriter ->
                 headers.addHeaderWriter(headerWriter)
             }

+ 41 - 0
config/src/main/kotlin/org/springframework/security/config/web/servlet/headers/PermissionsPolicyDsl.kt

@@ -0,0 +1,41 @@
+/*
+ * Copyright 2002-2020 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
+ *
+ *      https://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.config.web.servlet.headers
+
+import org.springframework.security.config.annotation.web.builders.HttpSecurity
+import org.springframework.security.config.annotation.web.configurers.HeadersConfigurer
+
+/**
+ * A Kotlin DSL to configure the [HttpSecurity] permissions policy header using
+ * idiomatic Kotlin code.
+ *
+ * @author Christophe Gilles
+ * @since 5.5
+ * @property policy the policy to be used in the response header.
+ */
+@HeadersSecurityMarker
+class PermissionsPolicyDsl {
+    var policy: String? = null
+
+    internal fun get(): (HeadersConfigurer<HttpSecurity>.PermissionsPolicyConfig) -> Unit {
+        return { permissionsPolicy ->
+            policy?.also {
+                permissionsPolicy.policy(policy)
+            }
+        }
+    }
+}

+ 8 - 1
config/src/main/resources/org/springframework/security/config/spring-security-5.5.rnc

@@ -919,7 +919,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? & feature-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? & permissions-policy? & header*)}
 headers-options.attlist &=
 	## Specifies if the default headers should be disabled. Default false.
 	attribute defaults-disabled {xsd:token}?
@@ -1007,6 +1007,13 @@ feature-options.attlist &=
 	## The security policy directive(s) for the Feature-Policy header.
 	attribute policy-directives {xsd:token}?
 
+permissions-policy =
+	## Adds support for Permissions Policy
+	element permissions-policy {permissions-options.attlist}
+permissions-options.attlist &=
+	## The policies for the Permissions-Policy header.
+	attribute policy {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}

+ 18 - 0
config/src/main/resources/org/springframework/security/config/spring-security-5.5.xsd

@@ -2692,6 +2692,7 @@
             <xs:element ref="security:content-security-policy"/>
             <xs:element ref="security:referrer-policy"/>
             <xs:element ref="security:feature-policy"/>
+            <xs:element ref="security:permissions-policy"/>
             <xs:element ref="security:header"/>
          </xs:choice>
          <xs:attributeGroup ref="security:headers-options.attlist"/>
@@ -2926,6 +2927,23 @@
          </xs:annotation>
       </xs:attribute>
   </xs:attributeGroup>
+  <xs:element name="permissions-policy">
+      <xs:annotation>
+         <xs:documentation>Adds support for Permissions Policy
+                </xs:documentation>
+      </xs:annotation>
+      <xs:complexType>
+         <xs:attributeGroup ref="security:permissions-options.attlist"/>
+      </xs:complexType>
+   </xs:element>
+  <xs:attributeGroup name="permissions-options.attlist">
+      <xs:attribute name="policy" type="xs:token">
+         <xs:annotation>
+            <xs:documentation>The policies for the Permissions-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

+ 100 - 0
config/src/test/java/org/springframework/security/config/annotation/web/configurers/HeadersConfigurerTests.java

@@ -447,6 +447,44 @@ public class HeadersConfigurerTests {
 				.withRootCauseInstanceOf(IllegalArgumentException.class);
 	}
 
+	@Test
+	public void getWhenPermissionsPolicyConfiguredThenPermissionsPolicyHeaderInResponse() throws Exception {
+		this.spring.register(PermissionsPolicyConfig.class).autowire();
+		ResultMatcher permissionsPolicy = header().string("Permissions-Policy", "geolocation=(self)");
+		// @formatter:off
+		MvcResult mvcResult = this.mvc.perform(get("/").secure(true))
+				.andExpect(permissionsPolicy)
+				.andReturn();
+		// @formatter:on
+		assertThat(mvcResult.getResponse().getHeaderNames()).containsExactly("Permissions-Policy");
+	}
+
+	@Test
+	public void getWhenPermissionsPolicyConfiguredWithStringThenPermissionsPolicyHeaderInResponse() throws Exception {
+		this.spring.register(PermissionsPolicyStringConfig.class).autowire();
+		ResultMatcher permissionsPolicy = header().string("Permissions-Policy", "geolocation=(self)");
+		// @formatter:off
+		MvcResult mvcResult = this.mvc.perform(get("/").secure(true))
+				.andExpect(permissionsPolicy)
+				.andReturn();
+		// @formatter:on
+		assertThat(mvcResult.getResponse().getHeaderNames()).containsExactly("Permissions-Policy");
+	}
+
+	@Test
+	public void configureWhenPermissionsPolicyEmptyThenException() {
+		assertThatExceptionOfType(BeanCreationException.class)
+				.isThrownBy(() -> this.spring.register(PermissionsPolicyInvalidConfig.class).autowire())
+				.withRootCauseInstanceOf(IllegalArgumentException.class);
+	}
+
+	@Test
+	public void configureWhenPermissionsPolicyStringEmptyThenException() {
+		assertThatExceptionOfType(BeanCreationException.class)
+				.isThrownBy(() -> this.spring.register(PermissionsPolicyInvalidStringConfig.class).autowire())
+				.withRootCauseInstanceOf(IllegalArgumentException.class);
+	}
+
 	@Test
 	public void getWhenHstsConfiguredWithPreloadThenStrictTransportSecurityHeaderWithPreloadInResponse()
 			throws Exception {
@@ -1012,6 +1050,68 @@ public class HeadersConfigurerTests {
 
 	}
 
+	@EnableWebSecurity
+	static class PermissionsPolicyConfig extends WebSecurityConfigurerAdapter {
+
+		@Override
+		protected void configure(HttpSecurity http) throws Exception {
+			// @formatter:off
+			http
+				.headers()
+					.defaultsDisabled()
+					.permissionsPolicy((permissionsPolicy) -> permissionsPolicy.policy("geolocation=(self)"));
+			// @formatter:on
+		}
+
+	}
+
+	@EnableWebSecurity
+	static class PermissionsPolicyStringConfig extends WebSecurityConfigurerAdapter {
+
+		@Override
+		protected void configure(HttpSecurity http) throws Exception {
+			// @formatter:off
+			http
+				.headers()
+					.defaultsDisabled()
+					.permissionsPolicy()
+					.policy("geolocation=(self)");
+			// @formatter:on
+		}
+
+	}
+
+	@EnableWebSecurity
+	static class PermissionsPolicyInvalidConfig extends WebSecurityConfigurerAdapter {
+
+		@Override
+		protected void configure(HttpSecurity http) throws Exception {
+			// @formatter:off
+			http
+				.headers()
+					.defaultsDisabled()
+					.permissionsPolicy((permissionsPolicy) -> permissionsPolicy.policy(null));
+			// @formatter:on
+		}
+
+	}
+
+	@EnableWebSecurity
+	static class PermissionsPolicyInvalidStringConfig extends WebSecurityConfigurerAdapter {
+
+		@Override
+		protected void configure(HttpSecurity http) throws Exception {
+			// @formatter:off
+			http
+				.headers()
+					.defaultsDisabled()
+					.permissionsPolicy()
+					.policy("");
+			// @formatter:on
+		}
+
+	}
+
 	@EnableWebSecurity
 	static class HstsWithPreloadConfig extends WebSecurityConfigurerAdapter {
 

+ 11 - 0
config/src/test/java/org/springframework/security/config/http/HttpHeadersConfigTests.java

@@ -332,6 +332,17 @@ public class HttpHeadersConfigTests {
 				() -> this.spring.configLocations(this.xml("DefaultsDisabledWithOnlyHeaderValue")).autowire());
 	}
 
+	@Test
+	public void requestWhenPermissionsPolicyConfiguredWithGeolocationSelfThenGeolocationSelf() throws Exception {
+		this.spring.configLocations(this.xml("DefaultsDisabledWithPermissionsPolicy")).autowire();
+		// @formatter:off
+		this.mvc.perform(get("/"))
+				.andExpect(status().isOk())
+				.andExpect(excludesDefaults())
+				.andExpect(header().string("Permissions-Policy", "geolocation=(self)"));
+		// @formatter:on
+	}
+
 	@Test
 	public void requestWhenUsingXssProtectionThenDefaultsToModeBlock() throws Exception {
 		Set<String> excludedHeaders = new HashSet<>(defaultHeaders.keySet());

+ 97 - 0
config/src/test/kotlin/org/springframework/security/config/web/server/ServerPermissionsPolicyDslTests.kt

@@ -0,0 +1,97 @@
+/*
+ * Copyright 2002-2020 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
+ *
+ *      https://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.config.web.server
+
+import org.junit.Rule
+import org.junit.Test
+import org.springframework.beans.factory.annotation.Autowired
+import org.springframework.context.ApplicationContext
+import org.springframework.context.annotation.Bean
+import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity
+import org.springframework.security.config.test.SpringTestRule
+import org.springframework.security.web.server.SecurityWebFilterChain
+import org.springframework.test.web.reactive.server.WebTestClient
+import org.springframework.web.reactive.config.EnableWebFlux
+
+/**
+ * Tests for [ServerPermissionsPolicyDsl]
+ *
+ * @author Christophe Gilles
+ */
+class ServerPermissionsPolicyDslTests {
+    @Rule
+    @JvmField
+    val spring = SpringTestRule()
+
+    private lateinit var client: WebTestClient
+
+    @Autowired
+    fun setup(context: ApplicationContext) {
+        this.client = WebTestClient
+                .bindToApplicationContext(context)
+                .configureClient()
+                .build()
+    }
+
+    @Test
+    fun `request when permissions policy configured then permissions policy header in response`() {
+        this.spring.register(PermissionsPolicyConfig::class.java).autowire()
+
+        this.client.get()
+                .uri("/")
+                .exchange()
+                .expectHeader().doesNotExist("Permissions-Policy")
+    }
+
+    @EnableWebFluxSecurity
+    @EnableWebFlux
+    open class PermissionsPolicyConfig {
+        @Bean
+        open fun springWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
+            return http {
+                headers {
+                    permissionsPolicy { }
+                }
+            }
+        }
+    }
+
+    @Test
+    fun `request when custom policy configured then custom policy in response header`() {
+        this.spring.register(CustomPolicyConfig::class.java).autowire()
+
+        this.client.get()
+                .uri("/")
+                .exchange()
+                .expectHeader().valueEquals("Permissions-Policy", "geolocation=(self)")
+    }
+
+    @EnableWebFluxSecurity
+    @EnableWebFlux
+    open class CustomPolicyConfig {
+        @Bean
+        open fun springWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
+            return http {
+                headers {
+                    permissionsPolicy {
+                        policy = "geolocation=(self)"
+                    }
+                }
+            }
+        }
+    }
+}

+ 24 - 0
config/src/test/kotlin/org/springframework/security/config/web/servlet/HeadersDslTests.kt

@@ -24,6 +24,7 @@ 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 org.springframework.security.config.test.SpringTestRule
+import org.springframework.security.config.web.servlet.headers.PermissionsPolicyDsl
 import org.springframework.security.web.header.writers.StaticHeadersWriter
 import org.springframework.security.web.header.writers.frameoptions.XFrameOptionsHeaderWriter
 import org.springframework.security.web.server.header.ContentTypeOptionsServerHttpHeadersWriter
@@ -93,6 +94,29 @@ class HeadersDslTests {
         }
     }
 
+    @Test
+    fun `headers when permissions policy configured then header in response`() {
+        this.spring.register(PermissionsPolicyConfig::class.java).autowire()
+
+        this.mockMvc.get("/")
+                .andExpect {
+                    header { string("Permissions-Policy", "geolocation=(self)") }
+                }
+    }
+
+    @EnableWebSecurity
+    open class PermissionsPolicyConfig : WebSecurityConfigurerAdapter() {
+        override fun configure(http: HttpSecurity) {
+            http {
+                headers {
+                    permissionsPolicy {
+                        policy = "geolocation=(self)"
+                    }
+                }
+            }
+        }
+    }
+
     @Test
     fun `request when headers disabled then no security headers are in the response`() {
         this.spring.register(HeadersDisabledConfig::class.java).autowire()

+ 36 - 0
config/src/test/resources/org/springframework/security/config/http/HttpHeadersConfigTests-DefaultsDisabledWithPermissionsPolicy.xml

@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  ~ Copyright 2002-2020 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
+  ~
+  ~      https://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.
+  -->
+
+<b:beans xmlns:b="http://www.springframework.org/schema/beans"
+		xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+		xmlns="http://www.springframework.org/schema/security"
+		xsi:schemaLocation="
+			http://www.springframework.org/schema/security
+			https://www.springframework.org/schema/security/spring-security.xsd
+			http://www.springframework.org/schema/beans
+			https://www.springframework.org/schema/beans/spring-beans.xsd">
+
+	<http auto-config="true">
+		<headers defaults-disabled="true">
+			<permissions-policy policy="geolocation=(self)"/>
+		</headers>
+	</http>
+
+	<b:bean name="simple" class="org.springframework.security.config.http.HttpHeadersConfigTests.SimpleController"/>
+
+	<b:import resource="userservice.xml"/>
+</b:beans>

+ 22 - 0
docs/manual/src/docs/asciidoc/_includes/about/exploits/headers.adoc

@@ -339,6 +339,28 @@ With Feature Policy, developers can opt-in to a set of "policies" for the browse
 These policies restrict what APIs the site can access or modify the browser's default behavior for certain features.
 
 
+[[headers-permissions]]
+== Permissions Policy
+
+[NOTE]
+====
+Refer to the relevant sections to see how to configure both <<servlet-headers-permissions,servlet>> and <<webflux-headers-permissions,webflux>> based applications.
+====
+
+https://w3c.github.io/webappsec-permissions-policy/[Permissions 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.
+
+.Permissions Policy Example
+====
+[source]
+----
+Permissions-Policy: geolocation=(self)
+----
+====
+
+With Permissions 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-clear-site-data]]
 == Clear Site Data
 

+ 52 - 0
docs/manual/src/docs/asciidoc/_includes/reactive/exploits/headers.adoc

@@ -472,6 +472,58 @@ fun webFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
 ====
 
 
+[[webflux-headers-permissions]]
+== Permissions Policy
+
+Spring Security does not add <<headers-permissions,Permissions Policy>> headers by default.
+The following `Permissions-Policy` header:
+
+.Permissions-Policy Example
+====
+[source]
+----
+Permissions-Policy: geolocation=(self)
+----
+====
+
+You can enable the Permissions Policy header as shown below:
+
+.Permissions-Policy Configuration
+====
+.Java
+[source,java,role="primary"]
+----
+@Bean
+SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
+	http
+		// ...
+		.headers(headers -> headers
+			.permissionsPolicy(permissions -> permissions
+				.policy("geolocation=(self)")
+			)
+		);
+	return http.build();
+}
+----
+
+.Kotlin
+[source,kotlin,role="secondary"]
+----
+@Bean
+fun webFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
+    return http {
+        // ...
+        headers {
+            permissionsPolicy {
+                policy = "geolocation=(self)"
+            }
+        }
+    }
+}
+----
+====
+
+
 [[webflux-headers-clear-site-data]]
 == Clear Site Data
 

+ 70 - 0
docs/manual/src/docs/asciidoc/_includes/servlet/exploits/headers.adoc

@@ -816,6 +816,76 @@ class SecurityConfig : WebSecurityConfigurerAdapter() {
 ----
 ====
 
+[[servlet-headers-permissions]]
+== Permissions Policy
+
+Spring Security does not add <<headers-permissions,Permissions Policy>> headers by default.
+The following `Permissions-Policy` header:
+
+.Permissions-Policy Example
+====
+[source]
+----
+Permissions-Policy: geolocation=(self)
+----
+====
+
+can enable the Permissions Policy header using the configuration shown below:
+
+.Permissions-Policy
+====
+.Java
+[source,java,role="primary"]
+----
+@EnableWebSecurity
+public class WebSecurityConfig extends
+WebSecurityConfigurerAdapter {
+
+	@Override
+	protected void configure(HttpSecurity http) throws Exception {
+		http
+			// ...
+			.headers(headers -> headers
+				.permissionsPolicy(permissions -> permissions
+					.policy("geolocation=(self)")
+				)
+			);
+	}
+}
+----
+
+.XML
+[source,xml,role="secondary"]
+----
+<http>
+	<!-- ... -->
+
+	<headers>
+		<permissions-policy policy="geolocation=(self)" />
+	</headers>
+</http>
+----
+
+.Kotlin
+[source,kotlin,role="secondary"]
+----
+@EnableWebSecurity
+class SecurityConfig : WebSecurityConfigurerAdapter() {
+
+    override fun configure(http: HttpSecurity) {
+        http {
+            // ...
+            headers {
+                permissionPolicy {
+                    policy = "geolocation=(self)"
+                }
+            }
+        }
+    }
+}
+----
+====
+
 [[servlet-headers-clear-site-data]]
 == Clear Site Data
 

+ 82 - 0
web/src/main/java/org/springframework/security/web/header/writers/PermissionsPolicyHeaderWriter.java

@@ -0,0 +1,82 @@
+/*
+ * Copyright 2002-2020 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
+ *
+ *      https://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://w3c.github.io/webappsec-permissions-policy//">Permisisons Policy</a>.
+ * <p>
+ * Permissions 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 permissions policy contains a set of security policies, each
+ * responsible for declaring the restrictions for a particular feature type.
+ *
+ * @author Christophe Gilles
+ * @since 5.5
+ */
+public final class PermissionsPolicyHeaderWriter implements HeaderWriter {
+
+	private static final String PERMISSIONS_POLICY_HEADER = "Permissions-Policy";
+
+	private String policy;
+
+	/**
+	 * Create a new instance of {@link PermissionsPolicyHeaderWriter}.
+	 */
+	public PermissionsPolicyHeaderWriter() {
+	}
+
+	/**
+	 * Create a new instance of {@link PermissionsPolicyHeaderWriter} with supplied
+	 * security policy.
+	 * @param policy the security policy
+	 * @throws IllegalArgumentException if policy is {@code null} or empty
+	 */
+	public PermissionsPolicyHeaderWriter(String policy) {
+		setPolicy(policy);
+	}
+
+	/**
+	 * Sets the policy to be used in the response header.
+	 * @param policy a permissions policy
+	 * @throws IllegalArgumentException if policy is null
+	 */
+	public void setPolicy(String policy) {
+		Assert.hasLength(policy, "policy can not be null or empty");
+		this.policy = policy;
+	}
+
+	@Override
+	public void writeHeaders(HttpServletRequest request, HttpServletResponse response) {
+		if (!response.containsHeader(PERMISSIONS_POLICY_HEADER)) {
+			response.setHeader(PERMISSIONS_POLICY_HEADER, this.policy);
+		}
+	}
+
+	@Override
+	public String toString() {
+		return getClass().getName() + " [policy=" + this.policy + "]";
+	}
+
+}

+ 59 - 0
web/src/main/java/org/springframework/security/web/server/header/PermissionsPolicyServerHttpHeadersWriter.java

@@ -0,0 +1,59 @@
+/*
+ * Copyright 2002-2020 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
+ *
+ *      https://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.server.header;
+
+import reactor.core.publisher.Mono;
+
+import org.springframework.security.web.server.header.StaticServerHttpHeadersWriter.Builder;
+import org.springframework.util.Assert;
+import org.springframework.web.server.ServerWebExchange;
+
+/**
+ * Writes the {@code Permissions-Policy} response header with configured policy
+ * directives.
+ *
+ * @author Christophe Gilles
+ * @since 5.5
+ */
+public final class PermissionsPolicyServerHttpHeadersWriter implements ServerHttpHeadersWriter {
+
+	public static final String PERMISSIONS_POLICY = "Permissions-Policy";
+
+	private ServerHttpHeadersWriter delegate;
+
+	@Override
+	public Mono<Void> writeHttpHeaders(ServerWebExchange exchange) {
+		return (this.delegate != null) ? this.delegate.writeHttpHeaders(exchange) : Mono.empty();
+	}
+
+	private static ServerHttpHeadersWriter createDelegate(String policyDirectives) {
+		Builder builder = StaticServerHttpHeadersWriter.builder();
+		builder.header(PERMISSIONS_POLICY, policyDirectives);
+		return builder.build();
+	}
+
+	/**
+	 * Set the policy to be used in the response header.
+	 * @param policy the policy
+	 * @throws IllegalArgumentException if policy is {@code null}
+	 */
+	public void setPolicy(String policy) {
+		Assert.notNull(policy, "policy must not be null");
+		this.delegate = createDelegate(policy);
+	}
+
+}

+ 79 - 0
web/src/test/java/org/springframework/security/web/header/writers/PermissionsPolicyHeaderWriterTests.java

@@ -0,0 +1,79 @@
+/*
+ * Copyright 2002-2020 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
+ *
+ *      https://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.assertThatIllegalArgumentException;
+
+/**
+ * Tests for {@link PermissionsPolicyHeaderWriter}.
+ *
+ * @author Christophe Gilles
+ */
+public class PermissionsPolicyHeaderWriterTests {
+
+	private static final String DEFAULT_POLICY_DIRECTIVES = "geolocation=(self)";
+
+	private MockHttpServletRequest request;
+
+	private MockHttpServletResponse response;
+
+	private PermissionsPolicyHeaderWriter writer;
+
+	private static final String PERMISSIONS_POLICY_HEADER = "Permissions-Policy";
+
+	@Before
+	public void setUp() {
+		this.request = new MockHttpServletRequest();
+		this.response = new MockHttpServletResponse();
+		this.writer = new PermissionsPolicyHeaderWriter(DEFAULT_POLICY_DIRECTIVES);
+	}
+
+	@Test
+	public void writeHeadersPermissionsPolicyDefault() {
+		this.writer.writeHeaders(this.request, this.response);
+		assertThat(this.response.getHeaderNames()).hasSize(1);
+		assertThat(this.response.getHeader("Permissions-Policy")).isEqualTo(DEFAULT_POLICY_DIRECTIVES);
+	}
+
+	@Test
+	public void createWriterWithNullPolicyShouldThrowException() {
+		assertThatIllegalArgumentException().isThrownBy(() -> new PermissionsPolicyHeaderWriter(null))
+				.withMessage("policy can not be null or empty");
+	}
+
+	@Test
+	public void createWriterWithEmptyPolicyShouldThrowException() {
+		assertThatIllegalArgumentException().isThrownBy(() -> new PermissionsPolicyHeaderWriter(""))
+				.withMessage("policy can not be null or empty");
+	}
+
+	@Test
+	public void writeHeaderOnlyIfNotPresent() {
+		String value = new String("value");
+		this.response.setHeader(PERMISSIONS_POLICY_HEADER, value);
+		this.writer.writeHeaders(this.request, this.response);
+		assertThat(this.response.getHeader(PERMISSIONS_POLICY_HEADER)).isSameAs(value);
+	}
+
+}

+ 77 - 0
web/src/test/java/org/springframework/security/web/server/header/PermissionsPolicyServerHttpHeadersWriterTests.java

@@ -0,0 +1,77 @@
+/*
+ * Copyright 2002-2020 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
+ *
+ *      https://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.server.header;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import org.springframework.http.HttpHeaders;
+import org.springframework.mock.http.server.reactive.MockServerHttpRequest;
+import org.springframework.mock.web.server.MockServerWebExchange;
+import org.springframework.web.server.ServerWebExchange;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+/**
+ * Tests for {@link PermissionsPolicyServerHttpHeadersWriter}.
+ *
+ * @author Christophe Gilles
+ */
+public class PermissionsPolicyServerHttpHeadersWriterTests {
+
+	private static final String DEFAULT_POLICY_DIRECTIVES = "geolocation=(self)";
+
+	private ServerWebExchange exchange;
+
+	private PermissionsPolicyServerHttpHeadersWriter writer;
+
+	@Before
+	public void setup() {
+		this.exchange = MockServerWebExchange.from(MockServerHttpRequest.get("/"));
+		this.writer = new PermissionsPolicyServerHttpHeadersWriter();
+	}
+
+	@Test
+	public void writeHeadersWhenUsingDefaultsThenDoesNotWrite() {
+		this.writer.writeHttpHeaders(this.exchange);
+		HttpHeaders headers = this.exchange.getResponse().getHeaders();
+		assertThat(headers).isEmpty();
+	}
+
+	@Test
+	public void writeHeadersWhenUsingPolicyThenWritesPolicy() {
+		this.writer.setPolicy(DEFAULT_POLICY_DIRECTIVES);
+		this.writer.writeHttpHeaders(this.exchange);
+		HttpHeaders headers = this.exchange.getResponse().getHeaders();
+		assertThat(headers).hasSize(1);
+		assertThat(headers.get(PermissionsPolicyServerHttpHeadersWriter.PERMISSIONS_POLICY))
+				.containsOnly(DEFAULT_POLICY_DIRECTIVES);
+	}
+
+	@Test
+	public void writeHeadersWhenAlreadyWrittenThenWritesHeader() {
+		this.writer.setPolicy(DEFAULT_POLICY_DIRECTIVES);
+		String headerValue = "camera=(self)";
+		this.exchange.getResponse().getHeaders().set(PermissionsPolicyServerHttpHeadersWriter.PERMISSIONS_POLICY,
+				headerValue);
+		this.writer.writeHttpHeaders(this.exchange);
+		HttpHeaders headers = this.exchange.getResponse().getHeaders();
+		assertThat(headers).hasSize(1);
+		assertThat(headers.get(PermissionsPolicyServerHttpHeadersWriter.PERMISSIONS_POLICY)).containsOnly(headerValue);
+	}
+
+}