mirror of
https://github.com/spring-projects/spring-security.git
synced 2025-05-30 16:52:13 +00:00
Add permissionsPolicy http header
This commit is contained in:
parent
48ef27b80a
commit
54d3839f63
@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -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);
|
||||||
|
@ -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.
|
||||||
*
|
*
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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)
|
||||||
}
|
}
|
||||||
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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}
|
||||||
|
@ -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
|
||||||
|
@ -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 {
|
||||||
|
|
||||||
|
@ -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());
|
||||||
|
@ -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,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()
|
||||||
|
@ -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>
|
@ -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
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
@ -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 + "]";
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user