瀏覽代碼

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.FeaturePolicyHeaderWriter;
 import org.springframework.security.web.header.writers.HpkpHeaderWriter;
 import org.springframework.security.web.header.writers.HpkpHeaderWriter;
 import org.springframework.security.web.header.writers.HstsHeaderWriter;
 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;
 import org.springframework.security.web.header.writers.ReferrerPolicyHeaderWriter.ReferrerPolicy;
 import org.springframework.security.web.header.writers.ReferrerPolicyHeaderWriter.ReferrerPolicy;
 import org.springframework.security.web.header.writers.XContentTypeOptionsHeaderWriter;
 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 FeaturePolicyConfig featurePolicy = new FeaturePolicyConfig();
 
 
+	private final PermissionsPolicyConfig permissionsPolicy = new PermissionsPolicyConfig();
+
 	/**
 	/**
 	 * Creates a new instance
 	 * Creates a new instance
 	 *
 	 *
@@ -387,6 +390,7 @@ public class HeadersConfigurer<H extends HttpSecurityBuilder<H>>
 		addIfNotNull(writers, this.contentSecurityPolicy.writer);
 		addIfNotNull(writers, this.contentSecurityPolicy.writer);
 		addIfNotNull(writers, this.referrerPolicy.writer);
 		addIfNotNull(writers, this.referrerPolicy.writer);
 		addIfNotNull(writers, this.featurePolicy.writer);
 		addIfNotNull(writers, this.featurePolicy.writer);
+		addIfNotNull(writers, this.permissionsPolicy.writer);
 		writers.addAll(this.headerWriters);
 		writers.addAll(this.headerWriters);
 		return writers;
 		return writers;
 	}
 	}
@@ -487,12 +491,58 @@ public class HeadersConfigurer<H extends HttpSecurityBuilder<H>>
 	 * @throws IllegalArgumentException if policyDirectives is {@code null} or empty
 	 * @throws IllegalArgumentException if policyDirectives is {@code null} or empty
 	 * @since 5.1
 	 * @since 5.1
 	 * @see FeaturePolicyHeaderWriter
 	 * @see FeaturePolicyHeaderWriter
+	 * @deprecated Use {@link #permissionsPolicy(Customizer)} instead.
 	 */
 	 */
+	@Deprecated
 	public FeaturePolicyConfig featurePolicy(String policyDirectives) {
 	public FeaturePolicyConfig featurePolicy(String policyDirectives) {
 		this.featurePolicy.writer = new FeaturePolicyHeaderWriter(policyDirectives);
 		this.featurePolicy.writer = new FeaturePolicyHeaderWriter(policyDirectives);
 		return this.featurePolicy;
 		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 {
 	public final class ContentTypeOptionsConfig {
 
 
 		private XContentTypeOptionsHeaderWriter writer;
 		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.FeaturePolicyHeaderWriter;
 import org.springframework.security.web.header.writers.HpkpHeaderWriter;
 import org.springframework.security.web.header.writers.HpkpHeaderWriter;
 import org.springframework.security.web.header.writers.HstsHeaderWriter;
 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;
 import org.springframework.security.web.header.writers.ReferrerPolicyHeaderWriter.ReferrerPolicy;
 import org.springframework.security.web.header.writers.ReferrerPolicyHeaderWriter.ReferrerPolicy;
 import org.springframework.security.web.header.writers.StaticHeadersWriter;
 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 FEATURE_POLICY_ELEMENT = "feature-policy";
 
 
+	private static final String PERMISSIONS_POLICY_ELEMENT = "permissions-policy";
+
 	private static final String ALLOW_FROM = "ALLOW-FROM";
 	private static final String ALLOW_FROM = "ALLOW-FROM";
 
 
 	private ManagedList<BeanMetadataElement> headerWriters;
 	private ManagedList<BeanMetadataElement> headerWriters;
@@ -140,6 +143,7 @@ public class HeadersBeanDefinitionParser implements BeanDefinitionParser {
 		parseContentSecurityPolicyElement(disabled, element, parserContext);
 		parseContentSecurityPolicyElement(disabled, element, parserContext);
 		parseReferrerPolicyElement(element, parserContext);
 		parseReferrerPolicyElement(element, parserContext);
 		parseFeaturePolicyElement(element, parserContext);
 		parseFeaturePolicyElement(element, parserContext);
+		parsePermissionsPolicyElement(element, parserContext);
 		parseHeaderElements(element);
 		parseHeaderElements(element);
 		boolean noWriters = this.headerWriters.isEmpty();
 		boolean noWriters = this.headerWriters.isEmpty();
 		if (disabled && !noWriters) {
 		if (disabled && !noWriters) {
@@ -351,6 +355,27 @@ public class HeadersBeanDefinitionParser implements BeanDefinitionParser {
 		this.headerWriters.add(headersWriter.getBeanDefinition());
 		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) {
 	private void attrNotAllowed(ParserContext context, String attrName, String otherAttrName, Element element) {
 		context.getReaderContext().error("Only one of '" + attrName + "' or '" + otherAttrName + "' can be set.",
 		context.getReaderContext().error("Only one of '" + attrName + "' or '" + otherAttrName + "' can be set.",
 				element);
 				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.ContentTypeOptionsServerHttpHeadersWriter;
 import org.springframework.security.web.server.header.FeaturePolicyServerHttpHeadersWriter;
 import org.springframework.security.web.server.header.FeaturePolicyServerHttpHeadersWriter;
 import org.springframework.security.web.server.header.HttpHeaderWriterWebFilter;
 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;
 import org.springframework.security.web.server.header.ReferrerPolicyServerHttpHeadersWriter.ReferrerPolicy;
 import org.springframework.security.web.server.header.ReferrerPolicyServerHttpHeadersWriter.ReferrerPolicy;
 import org.springframework.security.web.server.header.ServerHttpHeadersWriter;
 import org.springframework.security.web.server.header.ServerHttpHeadersWriter;
@@ -2232,13 +2233,16 @@ public class ServerHttpSecurity {
 
 
 		private FeaturePolicyServerHttpHeadersWriter featurePolicy = new FeaturePolicyServerHttpHeadersWriter();
 		private FeaturePolicyServerHttpHeadersWriter featurePolicy = new FeaturePolicyServerHttpHeadersWriter();
 
 
+		private PermissionsPolicyServerHttpHeadersWriter permissionsPolicy = new PermissionsPolicyServerHttpHeadersWriter();
+
 		private ContentSecurityPolicyServerHttpHeadersWriter contentSecurityPolicy = new ContentSecurityPolicyServerHttpHeadersWriter();
 		private ContentSecurityPolicyServerHttpHeadersWriter contentSecurityPolicy = new ContentSecurityPolicyServerHttpHeadersWriter();
 
 
 		private ReferrerPolicyServerHttpHeadersWriter referrerPolicy = new ReferrerPolicyServerHttpHeadersWriter();
 		private ReferrerPolicyServerHttpHeadersWriter referrerPolicy = new ReferrerPolicyServerHttpHeadersWriter();
 
 
 		private HeaderSpec() {
 		private HeaderSpec() {
 			this.writers = new ArrayList<>(Arrays.asList(this.cacheControl, this.contentTypeOptions, this.hsts,
 			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.
 		 * Configures {@code Feature-Policy} response header.
-		 * @param policyDirectives the policy directive(s)
+		 * @param policyDirectives the policy
 		 * @return the {@link FeaturePolicySpec} to configure
 		 * @return the {@link FeaturePolicySpec} to configure
 		 */
 		 */
 		public FeaturePolicySpec featurePolicy(String policyDirectives) {
 		public FeaturePolicySpec featurePolicy(String policyDirectives) {
 			return new FeaturePolicySpec(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.
 		 * Configures {@code Referrer-Policy} response header.
 		 * @param referrerPolicy the policy to use
 		 * @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.
 		 * 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 contentSecurityPolicy: ((ServerHttpSecurity.HeaderSpec.ContentSecurityPolicySpec) -> Unit)? = null
     private var referrerPolicy: ((ServerHttpSecurity.HeaderSpec.ReferrerPolicySpec) -> Unit)? = null
     private var referrerPolicy: ((ServerHttpSecurity.HeaderSpec.ReferrerPolicySpec) -> Unit)? = null
     private var featurePolicyDirectives: String? = null
     private var featurePolicyDirectives: String? = null
+    private var permissionsPolicy: ((ServerHttpSecurity.HeaderSpec.PermissionsPolicySpec) -> Unit)? = null
 
 
     private var disabled = false
     private var disabled = false
 
 
@@ -140,6 +141,21 @@ class ServerHeadersDsl {
         this.featurePolicyDirectives = policyDirectives
         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.
      * Disables HTTP response headers.
      */
      */
@@ -170,6 +186,9 @@ class ServerHeadersDsl {
             featurePolicyDirectives?.also {
             featurePolicyDirectives?.also {
                 headers.featurePolicy(featurePolicyDirectives)
                 headers.featurePolicy(featurePolicyDirectives)
             }
             }
+            permissionsPolicy?.also {
+                headers.permissionsPolicy(permissionsPolicy)
+            }
             referrerPolicy?.also {
             referrerPolicy?.also {
                 headers.referrerPolicy(referrerPolicy)
                 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 contentSecurityPolicy: ((HeadersConfigurer<HttpSecurity>.ContentSecurityPolicyConfig) -> Unit)? = null
     private var referrerPolicy: ((HeadersConfigurer<HttpSecurity>.ReferrerPolicyConfig) -> Unit)? = null
     private var referrerPolicy: ((HeadersConfigurer<HttpSecurity>.ReferrerPolicyConfig) -> Unit)? = null
     private var featurePolicyDirectives: String? = null
     private var featurePolicyDirectives: String? = null
+    private var permissionsPolicy: ((HeadersConfigurer<HttpSecurity>.PermissionsPolicyConfig) -> Unit)? = null
     private var disabled = false
     private var disabled = false
     private var headerWriters = mutableListOf<HeaderWriter>()
     private var headerWriters = mutableListOf<HeaderWriter>()
 
 
@@ -164,6 +165,21 @@ class HeadersDsl {
         this.featurePolicyDirectives = policyDirectives
         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.
      * Adds a [HeaderWriter] instance.
      *
      *
@@ -217,6 +233,9 @@ class HeadersDsl {
             featurePolicyDirectives?.also {
             featurePolicyDirectives?.also {
                 headers.featurePolicy(featurePolicyDirectives)
                 headers.featurePolicy(featurePolicyDirectives)
             }
             }
+            permissionsPolicy?.also {
+                headers.permissionsPolicy(permissionsPolicy)
+            }
             headerWriters.forEach { headerWriter ->
             headerWriters.forEach { headerWriter ->
                 headers.addHeaderWriter(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 =
 headers =
 ## Element for configuration of the HeaderWritersFilter. Enables easy setting for the X-Frame-Options, X-XSS-Protection and X-Content-Type-Options headers.
 ## Element for configuration of the HeaderWritersFilter. Enables easy setting for the X-Frame-Options, X-XSS-Protection and X-Content-Type-Options headers.
-element headers { headers-options.attlist, (cache-control? & xss-protection? & hsts? & frame-options? & content-type-options? & hpkp? & 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 &=
 headers-options.attlist &=
 	## Specifies if the default headers should be disabled. Default false.
 	## Specifies if the default headers should be disabled. Default false.
 	attribute defaults-disabled {xsd:token}?
 	attribute defaults-disabled {xsd:token}?
@@ -1007,6 +1007,13 @@ feature-options.attlist &=
 	## The security policy directive(s) for the Feature-Policy header.
 	## The security policy directive(s) for the Feature-Policy header.
 	attribute policy-directives {xsd:token}?
 	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 =
 cache-control =
 	## Adds Cache-Control no-cache, no-store, must-revalidate, Pragma no-cache, and Expires 0 for every request
 	## Adds Cache-Control no-cache, no-store, must-revalidate, Pragma no-cache, and Expires 0 for every request
 	element cache-control {cache-control.attlist}
 	element cache-control {cache-control.attlist}

+ 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:content-security-policy"/>
             <xs:element ref="security:referrer-policy"/>
             <xs:element ref="security:referrer-policy"/>
             <xs:element ref="security:feature-policy"/>
             <xs:element ref="security:feature-policy"/>
+            <xs:element ref="security:permissions-policy"/>
             <xs:element ref="security:header"/>
             <xs:element ref="security:header"/>
          </xs:choice>
          </xs:choice>
          <xs:attributeGroup ref="security:headers-options.attlist"/>
          <xs:attributeGroup ref="security:headers-options.attlist"/>
@@ -2926,6 +2927,23 @@
          </xs:annotation>
          </xs:annotation>
       </xs:attribute>
       </xs:attribute>
   </xs:attributeGroup>
   </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:element name="cache-control">
       <xs:annotation>
       <xs:annotation>
          <xs:documentation>Adds Cache-Control no-cache, no-store, must-revalidate, Pragma no-cache, and Expires 0 for
          <xs:documentation>Adds Cache-Control no-cache, no-store, must-revalidate, Pragma no-cache, and Expires 0 for

+ 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);
 				.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
 	@Test
 	public void getWhenHstsConfiguredWithPreloadThenStrictTransportSecurityHeaderWithPreloadInResponse()
 	public void getWhenHstsConfiguredWithPreloadThenStrictTransportSecurityHeaderWithPreloadInResponse()
 			throws Exception {
 			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
 	@EnableWebSecurity
 	static class HstsWithPreloadConfig extends WebSecurityConfigurerAdapter {
 	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());
 				() -> 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
 	@Test
 	public void requestWhenUsingXssProtectionThenDefaultsToModeBlock() throws Exception {
 	public void requestWhenUsingXssProtectionThenDefaultsToModeBlock() throws Exception {
 		Set<String> excludedHeaders = new HashSet<>(defaultHeaders.keySet());
 		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.EnableWebSecurity
 import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter
 import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter
 import org.springframework.security.config.test.SpringTestRule
 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.StaticHeadersWriter
 import org.springframework.security.web.header.writers.frameoptions.XFrameOptionsHeaderWriter
 import org.springframework.security.web.header.writers.frameoptions.XFrameOptionsHeaderWriter
 import org.springframework.security.web.server.header.ContentTypeOptionsServerHttpHeadersWriter
 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
     @Test
     fun `request when headers disabled then no security headers are in the response`() {
     fun `request when headers disabled then no security headers are in the response`() {
         this.spring.register(HeadersDisabledConfig::class.java).autowire()
         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.
 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]]
 [[headers-clear-site-data]]
 == 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]]
 [[webflux-headers-clear-site-data]]
 == 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]]
 [[servlet-headers-clear-site-data]]
 == 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);
+	}
+
+}