2
0
Эх сурвалжийг харах

Add Cross Origin Policies headers

Add DSL support for Cross-Origin-Opener-Policy, Cross-Origin-Embedder-Policy and Cross-Origin-Resource-Policy headers

Closes gh-9385, gh-10118
Marcus Da Coregio 3 жил өмнө
parent
commit
65426a40ec
38 өөрчлөгдсөн 2513 нэмэгдсэн , 9 устгасан
  1. 228 1
      config/src/main/java/org/springframework/security/config/annotation/web/configurers/HeadersConfigurer.java
  2. 82 1
      config/src/main/java/org/springframework/security/config/http/HeadersBeanDefinitionParser.java
  3. 185 1
      config/src/main/java/org/springframework/security/config/web/server/ServerHttpSecurity.java
  4. 42 0
      config/src/main/kotlin/org/springframework/security/config/web/server/ServerCrossOriginEmbedderPolicyDsl.kt
  5. 42 0
      config/src/main/kotlin/org/springframework/security/config/web/server/ServerCrossOriginOpenerPolicyDsl.kt
  6. 42 0
      config/src/main/kotlin/org/springframework/security/config/web/server/ServerCrossOriginResourcePolicyDsl.kt
  7. 51 1
      config/src/main/kotlin/org/springframework/security/config/web/server/ServerHeadersDsl.kt
  8. 60 0
      config/src/main/kotlin/org/springframework/security/config/web/servlet/HeadersDsl.kt
  9. 43 0
      config/src/main/kotlin/org/springframework/security/config/web/servlet/headers/CrossOriginEmbedderPolicyDsl.kt
  10. 43 0
      config/src/main/kotlin/org/springframework/security/config/web/servlet/headers/CrossOriginOpenerPolicyDsl.kt
  11. 43 0
      config/src/main/kotlin/org/springframework/security/config/web/servlet/headers/CrossOriginResourcePolicyDsl.kt
  12. 22 1
      config/src/main/resources/org/springframework/security/config/spring-security-5.7.rnc
  13. 74 0
      config/src/main/resources/org/springframework/security/config/spring-security-5.7.xsd
  14. 77 1
      config/src/test/java/org/springframework/security/config/annotation/web/configurers/HeadersConfigurerTests.java
  15. 49 1
      config/src/test/java/org/springframework/security/config/http/HttpHeadersConfigTests.java
  16. 52 1
      config/src/test/java/org/springframework/security/config/web/server/HeaderSpecTests.java
  17. 59 0
      config/src/test/kotlin/org/springframework/security/config/web/server/ServerHeadersDslTests.kt
  18. 1 1
      config/src/test/kotlin/org/springframework/security/config/web/servlet/HeadersDslTests.kt
  19. 36 0
      config/src/test/resources/org/springframework/security/config/http/HttpHeadersConfigTests-DefaultsDisabledWithCrossOriginEmbedderPolicy.xml
  20. 36 0
      config/src/test/resources/org/springframework/security/config/http/HttpHeadersConfigTests-DefaultsDisabledWithCrossOriginOpenerPolicy.xml
  21. 38 0
      config/src/test/resources/org/springframework/security/config/http/HttpHeadersConfigTests-DefaultsDisabledWithCrossOriginPolicies.xml
  22. 36 0
      config/src/test/resources/org/springframework/security/config/http/HttpHeadersConfigTests-DefaultsDisabledWithCrossOriginResourcePolicy.xml
  23. 20 0
      docs/modules/ROOT/pages/features/exploits/headers.adoc
  24. 62 0
      docs/modules/ROOT/pages/reactive/exploits/headers.adoc
  25. 66 0
      docs/modules/ROOT/pages/servlet/appendix/namespace/http.adoc
  26. 61 0
      docs/modules/ROOT/pages/servlet/exploits/headers.adoc
  27. 84 0
      web/src/main/java/org/springframework/security/web/header/writers/CrossOriginEmbedderPolicyHeaderWriter.java
  28. 86 0
      web/src/main/java/org/springframework/security/web/header/writers/CrossOriginOpenerPolicyHeaderWriter.java
  29. 86 0
      web/src/main/java/org/springframework/security/web/header/writers/CrossOriginResourcePolicyHeaderWriter.java
  30. 78 0
      web/src/main/java/org/springframework/security/web/server/header/CrossOriginEmbedderPolicyServerHttpHeadersWriter.java
  31. 80 0
      web/src/main/java/org/springframework/security/web/server/header/CrossOriginOpenerPolicyServerHttpHeadersWriter.java
  32. 80 0
      web/src/main/java/org/springframework/security/web/server/header/CrossOriginResourcePolicyServerHttpHeadersWriter.java
  33. 80 0
      web/src/test/java/org/springframework/security/web/header/writers/CrossOriginEmbedderPolicyHeaderWriterTests.java
  34. 80 0
      web/src/test/java/org/springframework/security/web/header/writers/CrossOriginOpenerPolicyHeaderWriterTests.java
  35. 80 0
      web/src/test/java/org/springframework/security/web/header/writers/CrossOriginResourcePolicyHeaderWriterTests.java
  36. 76 0
      web/src/test/java/org/springframework/security/web/server/header/CrossOriginEmbedderPolicyServerHttpHeadersWriterTests.java
  37. 77 0
      web/src/test/java/org/springframework/security/web/server/header/CrossOriginOpenerPolicyServerHttpHeadersWriterTests.java
  38. 76 0
      web/src/test/java/org/springframework/security/web/server/header/CrossOriginResourcePolicyServerHttpHeadersWriterTests.java

+ 228 - 1
config/src/main/java/org/springframework/security/config/annotation/web/configurers/HeadersConfigurer.java

@@ -1,5 +1,5 @@
 /*
- * Copyright 2002-2019 the original author or authors.
+ * Copyright 2002-2021 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.
@@ -31,6 +31,9 @@ import org.springframework.security.web.header.HeaderWriter;
 import org.springframework.security.web.header.HeaderWriterFilter;
 import org.springframework.security.web.header.writers.CacheControlHeadersWriter;
 import org.springframework.security.web.header.writers.ContentSecurityPolicyHeaderWriter;
+import org.springframework.security.web.header.writers.CrossOriginEmbedderPolicyHeaderWriter;
+import org.springframework.security.web.header.writers.CrossOriginOpenerPolicyHeaderWriter;
+import org.springframework.security.web.header.writers.CrossOriginResourcePolicyHeaderWriter;
 import org.springframework.security.web.header.writers.FeaturePolicyHeaderWriter;
 import org.springframework.security.web.header.writers.HpkpHeaderWriter;
 import org.springframework.security.web.header.writers.HstsHeaderWriter;
@@ -97,6 +100,12 @@ public class HeadersConfigurer<H extends HttpSecurityBuilder<H>>
 
 	private final PermissionsPolicyConfig permissionsPolicy = new PermissionsPolicyConfig();
 
+	private final CrossOriginOpenerPolicyConfig crossOriginOpenerPolicy = new CrossOriginOpenerPolicyConfig();
+
+	private final CrossOriginEmbedderPolicyConfig crossOriginEmbedderPolicy = new CrossOriginEmbedderPolicyConfig();
+
+	private final CrossOriginResourcePolicyConfig crossOriginResourcePolicy = new CrossOriginResourcePolicyConfig();
+
 	/**
 	 * Creates a new instance
 	 *
@@ -392,6 +401,9 @@ public class HeadersConfigurer<H extends HttpSecurityBuilder<H>>
 		addIfNotNull(writers, this.referrerPolicy.writer);
 		addIfNotNull(writers, this.featurePolicy.writer);
 		addIfNotNull(writers, this.permissionsPolicy.writer);
+		addIfNotNull(writers, this.crossOriginOpenerPolicy.writer);
+		addIfNotNull(writers, this.crossOriginEmbedderPolicy.writer);
+		addIfNotNull(writers, this.crossOriginResourcePolicy.writer);
 		writers.addAll(this.headerWriters);
 		return writers;
 	}
@@ -544,6 +556,129 @@ public class HeadersConfigurer<H extends HttpSecurityBuilder<H>>
 		return this.permissionsPolicy;
 	}
 
+	/**
+	 * Allows configuration for <a href=
+	 * "https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cross-Origin-Opener-Policy">
+	 * Cross-Origin-Opener-Policy</a> header.
+	 * <p>
+	 * Configuration is provided to the {@link CrossOriginOpenerPolicyHeaderWriter} which
+	 * responsible for writing the header.
+	 * </p>
+	 * @return the {@link CrossOriginOpenerPolicyConfig} for additional confniguration
+	 * @since 5.7
+	 * @see CrossOriginOpenerPolicyHeaderWriter
+	 */
+	public CrossOriginOpenerPolicyConfig crossOriginOpenerPolicy() {
+		this.crossOriginOpenerPolicy.writer = new CrossOriginOpenerPolicyHeaderWriter();
+		return this.crossOriginOpenerPolicy;
+	}
+
+	/**
+	 * Allows configuration for <a href=
+	 * "https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cross-Origin-Opener-Policy">
+	 * Cross-Origin-Opener-Policy</a> header.
+	 * <p>
+	 * Calling this method automatically enables (includes) the
+	 * {@code Cross-Origin-Opener-Policy} header in the response using the supplied
+	 * policy.
+	 * <p>
+	 * <p>
+	 * Configuration is provided to the {@link CrossOriginOpenerPolicyHeaderWriter} which
+	 * responsible for writing the header.
+	 * </p>
+	 * @return the {@link HeadersConfigurer} for additional customizations
+	 * @since 5.7
+	 * @see CrossOriginOpenerPolicyHeaderWriter
+	 */
+	public HeadersConfigurer<H> crossOriginOpenerPolicy(
+			Customizer<CrossOriginOpenerPolicyConfig> crossOriginOpenerPolicyCustomizer) {
+		this.crossOriginOpenerPolicy.writer = new CrossOriginOpenerPolicyHeaderWriter();
+		crossOriginOpenerPolicyCustomizer.customize(this.crossOriginOpenerPolicy);
+		return HeadersConfigurer.this;
+	}
+
+	/**
+	 * Allows configuration for <a href=
+	 * "https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cross-Origin-Embedder-Policy">
+	 * Cross-Origin-Embedder-Policy</a> header.
+	 * <p>
+	 * Configuration is provided to the {@link CrossOriginEmbedderPolicyHeaderWriter}
+	 * which is responsible for writing the header.
+	 * </p>
+	 * @return the {@link CrossOriginEmbedderPolicyConfig} for additional customizations
+	 * @since 5.7
+	 * @see CrossOriginEmbedderPolicyHeaderWriter
+	 */
+	public CrossOriginEmbedderPolicyConfig crossOriginEmbedderPolicy() {
+		this.crossOriginEmbedderPolicy.writer = new CrossOriginEmbedderPolicyHeaderWriter();
+		return this.crossOriginEmbedderPolicy;
+	}
+
+	/**
+	 * Allows configuration for <a href=
+	 * "https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cross-Origin-Embedder-Policy">
+	 * Cross-Origin-Embedder-Policy</a> header.
+	 * <p>
+	 * Calling this method automatically enables (includes) the
+	 * {@code Cross-Origin-Embedder-Policy} header in the response using the supplied
+	 * policy.
+	 * <p>
+	 * <p>
+	 * Configuration is provided to the {@link CrossOriginEmbedderPolicyHeaderWriter}
+	 * which is responsible for writing the header.
+	 * </p>
+	 * @return the {@link HeadersConfigurer} for additional customizations
+	 * @since 5.7
+	 * @see CrossOriginEmbedderPolicyHeaderWriter
+	 */
+	public HeadersConfigurer<H> crossOriginEmbedderPolicy(
+			Customizer<CrossOriginEmbedderPolicyConfig> crossOriginEmbedderPolicyCustomizer) {
+		this.crossOriginEmbedderPolicy.writer = new CrossOriginEmbedderPolicyHeaderWriter();
+		crossOriginEmbedderPolicyCustomizer.customize(this.crossOriginEmbedderPolicy);
+		return HeadersConfigurer.this;
+	}
+
+	/**
+	 * Allows configuration for <a href=
+	 * "https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cross-Origin-Resource-Policy">
+	 * Cross-Origin-Resource-Policy</a> header.
+	 * <p>
+	 * Configuration is provided to the {@link CrossOriginResourcePolicyHeaderWriter}
+	 * which is responsible for writing the header:
+	 * </p>
+	 * @return the {@link HeadersConfigurer} for additional customizations
+	 * @since 5.7
+	 * @see CrossOriginResourcePolicyHeaderWriter
+	 */
+	public CrossOriginResourcePolicyConfig crossOriginResourcePolicy() {
+		this.crossOriginResourcePolicy.writer = new CrossOriginResourcePolicyHeaderWriter();
+		return this.crossOriginResourcePolicy;
+	}
+
+	/**
+	 * Allows configuration for <a href=
+	 * "https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cross-Origin-Resource-Policy">
+	 * Cross-Origin-Resource-Policy</a> header.
+	 * <p>
+	 * Calling this method automatically enables (includes) the
+	 * {@code Cross-Origin-Resource-Policy} header in the response using the supplied
+	 * policy.
+	 * <p>
+	 * <p>
+	 * Configuration is provided to the {@link CrossOriginResourcePolicyHeaderWriter}
+	 * which is responsible for writing the header:
+	 * </p>
+	 * @return the {@link HeadersConfigurer} for additional customizations
+	 * @since 5.7
+	 * @see CrossOriginResourcePolicyHeaderWriter
+	 */
+	public HeadersConfigurer<H> crossOriginResourcePolicy(
+			Customizer<CrossOriginResourcePolicyConfig> crossOriginResourcePolicyCustomizer) {
+		this.crossOriginResourcePolicy.writer = new CrossOriginResourcePolicyHeaderWriter();
+		crossOriginResourcePolicyCustomizer.customize(this.crossOriginResourcePolicy);
+		return HeadersConfigurer.this;
+	}
+
 	public final class ContentTypeOptionsConfig {
 
 		private XContentTypeOptionsHeaderWriter writer;
@@ -1142,4 +1277,96 @@ public class HeadersConfigurer<H extends HttpSecurityBuilder<H>>
 
 	}
 
+	public final class CrossOriginOpenerPolicyConfig {
+
+		private CrossOriginOpenerPolicyHeaderWriter writer;
+
+		public CrossOriginOpenerPolicyConfig() {
+		}
+
+		/**
+		 * Sets the policy to be used in the {@code Cross-Origin-Opener-Policy} header
+		 * @param openerPolicy a {@code Cross-Origin-Opener-Policy}
+		 * @return the {@link CrossOriginOpenerPolicyConfig} for additional configuration
+		 * @throws IllegalArgumentException if openerPolicy is null
+		 */
+		public CrossOriginOpenerPolicyConfig policy(
+				CrossOriginOpenerPolicyHeaderWriter.CrossOriginOpenerPolicy openerPolicy) {
+			this.writer.setPolicy(openerPolicy);
+			return this;
+		}
+
+		/**
+		 * Allows completing configuration of Cross Origin Opener Policy and continuing
+		 * configuration of headers.
+		 * @return the {@link HeadersConfigurer} for additional configuration
+		 */
+		public HeadersConfigurer<H> and() {
+			return HeadersConfigurer.this;
+		}
+
+	}
+
+	public final class CrossOriginEmbedderPolicyConfig {
+
+		private CrossOriginEmbedderPolicyHeaderWriter writer;
+
+		public CrossOriginEmbedderPolicyConfig() {
+		}
+
+		/**
+		 * Sets the policy to be used in the {@code Cross-Origin-Embedder-Policy} header
+		 * @param embedderPolicy a {@code Cross-Origin-Embedder-Policy}
+		 * @return the {@link CrossOriginEmbedderPolicyConfig} for additional
+		 * configuration
+		 * @throws IllegalArgumentException if embedderPolicy is null
+		 */
+		public CrossOriginEmbedderPolicyConfig policy(
+				CrossOriginEmbedderPolicyHeaderWriter.CrossOriginEmbedderPolicy embedderPolicy) {
+			this.writer.setPolicy(embedderPolicy);
+			return this;
+		}
+
+		/**
+		 * Allows completing configuration of Cross-Origin-Embedder-Policy and continuing
+		 * configuration of headers.
+		 * @return the {@link HeadersConfigurer} for additional configuration
+		 */
+		public HeadersConfigurer<H> and() {
+			return HeadersConfigurer.this;
+		}
+
+	}
+
+	public final class CrossOriginResourcePolicyConfig {
+
+		private CrossOriginResourcePolicyHeaderWriter writer;
+
+		public CrossOriginResourcePolicyConfig() {
+		}
+
+		/**
+		 * Sets the policy to be used in the {@code Cross-Origin-Resource-Policy} header
+		 * @param resourcePolicy a {@code Cross-Origin-Resource-Policy}
+		 * @return the {@link CrossOriginResourcePolicyConfig} for additional
+		 * configuration
+		 * @throws IllegalArgumentException if resourcePolicy is null
+		 */
+		public CrossOriginResourcePolicyConfig policy(
+				CrossOriginResourcePolicyHeaderWriter.CrossOriginResourcePolicy resourcePolicy) {
+			this.writer.setPolicy(resourcePolicy);
+			return this;
+		}
+
+		/**
+		 * Allows completing configuration of Cross-Origin-Resource-Policy and continuing
+		 * configuration of headers.
+		 * @return the {@link HeadersConfigurer} for additional configuration
+		 */
+		public HeadersConfigurer<H> and() {
+			return HeadersConfigurer.this;
+		}
+
+	}
+
 }

+ 82 - 1
config/src/main/java/org/springframework/security/config/http/HeadersBeanDefinitionParser.java

@@ -1,5 +1,5 @@
 /*
- * Copyright 2002-2019 the original author or authors.
+ * Copyright 2002-2021 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.
@@ -36,6 +36,9 @@ import org.springframework.beans.factory.xml.ParserContext;
 import org.springframework.security.web.header.HeaderWriterFilter;
 import org.springframework.security.web.header.writers.CacheControlHeadersWriter;
 import org.springframework.security.web.header.writers.ContentSecurityPolicyHeaderWriter;
+import org.springframework.security.web.header.writers.CrossOriginEmbedderPolicyHeaderWriter;
+import org.springframework.security.web.header.writers.CrossOriginOpenerPolicyHeaderWriter;
+import org.springframework.security.web.header.writers.CrossOriginResourcePolicyHeaderWriter;
 import org.springframework.security.web.header.writers.FeaturePolicyHeaderWriter;
 import org.springframework.security.web.header.writers.HpkpHeaderWriter;
 import org.springframework.security.web.header.writers.HstsHeaderWriter;
@@ -122,6 +125,12 @@ public class HeadersBeanDefinitionParser implements BeanDefinitionParser {
 
 	private static final String PERMISSIONS_POLICY_ELEMENT = "permissions-policy";
 
+	private static final String CROSS_ORIGIN_OPENER_POLICY_ELEMENT = "cross-origin-opener-policy";
+
+	private static final String CROSS_ORIGIN_EMBEDDER_POLICY_ELEMENT = "cross-origin-embedder-policy";
+
+	private static final String CROSS_ORIGIN_RESOURCE_POLICY_ELEMENT = "cross-origin-resource-policy";
+
 	private static final String ALLOW_FROM = "ALLOW-FROM";
 
 	private ManagedList<BeanMetadataElement> headerWriters;
@@ -144,6 +153,9 @@ public class HeadersBeanDefinitionParser implements BeanDefinitionParser {
 		parseReferrerPolicyElement(element, parserContext);
 		parseFeaturePolicyElement(element, parserContext);
 		parsePermissionsPolicyElement(element, parserContext);
+		parseCrossOriginOpenerPolicy(disabled, element);
+		parseCrossOriginEmbedderPolicy(disabled, element);
+		parseCrossOriginResourcePolicy(disabled, element);
 		parseHeaderElements(element);
 		boolean noWriters = this.headerWriters.isEmpty();
 		if (disabled && !noWriters) {
@@ -376,6 +388,75 @@ public class HeadersBeanDefinitionParser implements BeanDefinitionParser {
 		this.headerWriters.add(headersWriter.getBeanDefinition());
 	}
 
+	private void parseCrossOriginOpenerPolicy(boolean elementDisabled, Element element) {
+		if (elementDisabled || element == null) {
+			return;
+		}
+		CrossOriginOpenerPolicyHeaderWriter writer = new CrossOriginOpenerPolicyHeaderWriter();
+		Element crossOriginOpenerPolicyElement = DomUtils.getChildElementByTagName(element,
+				CROSS_ORIGIN_OPENER_POLICY_ELEMENT);
+		if (crossOriginOpenerPolicyElement != null) {
+			addCrossOriginOpenerPolicy(crossOriginOpenerPolicyElement, writer);
+		}
+		BeanDefinitionBuilder builder = BeanDefinitionBuilder
+				.genericBeanDefinition(CrossOriginOpenerPolicyHeaderWriter.class, () -> writer);
+		this.headerWriters.add(builder.getBeanDefinition());
+	}
+
+	private void parseCrossOriginEmbedderPolicy(boolean elementDisabled, Element element) {
+		if (elementDisabled || element == null) {
+			return;
+		}
+		CrossOriginEmbedderPolicyHeaderWriter writer = new CrossOriginEmbedderPolicyHeaderWriter();
+		Element crossOriginEmbedderPolicyElement = DomUtils.getChildElementByTagName(element,
+				CROSS_ORIGIN_EMBEDDER_POLICY_ELEMENT);
+		if (crossOriginEmbedderPolicyElement != null) {
+			addCrossOriginEmbedderPolicy(crossOriginEmbedderPolicyElement, writer);
+		}
+		BeanDefinitionBuilder builder = BeanDefinitionBuilder
+				.genericBeanDefinition(CrossOriginEmbedderPolicyHeaderWriter.class, () -> writer);
+		this.headerWriters.add(builder.getBeanDefinition());
+	}
+
+	private void parseCrossOriginResourcePolicy(boolean elementDisabled, Element element) {
+		if (elementDisabled || element == null) {
+			return;
+		}
+		CrossOriginResourcePolicyHeaderWriter writer = new CrossOriginResourcePolicyHeaderWriter();
+		Element crossOriginResourcePolicyElement = DomUtils.getChildElementByTagName(element,
+				CROSS_ORIGIN_RESOURCE_POLICY_ELEMENT);
+		if (crossOriginResourcePolicyElement != null) {
+			addCrossOriginResourcePolicy(crossOriginResourcePolicyElement, writer);
+		}
+		BeanDefinitionBuilder builder = BeanDefinitionBuilder
+				.genericBeanDefinition(CrossOriginResourcePolicyHeaderWriter.class, () -> writer);
+		this.headerWriters.add(builder.getBeanDefinition());
+	}
+
+	private void addCrossOriginResourcePolicy(Element crossOriginResourcePolicyElement,
+			CrossOriginResourcePolicyHeaderWriter writer) {
+		String policy = crossOriginResourcePolicyElement.getAttribute(ATT_POLICY);
+		if (StringUtils.hasText(policy)) {
+			writer.setPolicy(CrossOriginResourcePolicyHeaderWriter.CrossOriginResourcePolicy.from(policy));
+		}
+	}
+
+	private void addCrossOriginEmbedderPolicy(Element crossOriginEmbedderPolicyElement,
+			CrossOriginEmbedderPolicyHeaderWriter writer) {
+		String policy = crossOriginEmbedderPolicyElement.getAttribute(ATT_POLICY);
+		if (StringUtils.hasText(policy)) {
+			writer.setPolicy(CrossOriginEmbedderPolicyHeaderWriter.CrossOriginEmbedderPolicy.from(policy));
+		}
+	}
+
+	private void addCrossOriginOpenerPolicy(Element crossOriginOpenerPolicyElement,
+			CrossOriginOpenerPolicyHeaderWriter writer) {
+		String policy = crossOriginOpenerPolicyElement.getAttribute(ATT_POLICY);
+		if (StringUtils.hasText(policy)) {
+			writer.setPolicy(CrossOriginOpenerPolicyHeaderWriter.CrossOriginOpenerPolicy.from(policy));
+		}
+	}
+
 	private void attrNotAllowed(ParserContext context, String attrName, String otherAttrName, Element element) {
 		context.getReaderContext().error("Only one of '" + attrName + "' or '" + otherAttrName + "' can be set.",
 				element);

+ 185 - 1
config/src/main/java/org/springframework/security/config/web/server/ServerHttpSecurity.java

@@ -149,6 +149,12 @@ import org.springframework.security.web.server.header.CacheControlServerHttpHead
 import org.springframework.security.web.server.header.CompositeServerHttpHeadersWriter;
 import org.springframework.security.web.server.header.ContentSecurityPolicyServerHttpHeadersWriter;
 import org.springframework.security.web.server.header.ContentTypeOptionsServerHttpHeadersWriter;
+import org.springframework.security.web.server.header.CrossOriginEmbedderPolicyServerHttpHeadersWriter;
+import org.springframework.security.web.server.header.CrossOriginEmbedderPolicyServerHttpHeadersWriter.CrossOriginEmbedderPolicy;
+import org.springframework.security.web.server.header.CrossOriginOpenerPolicyServerHttpHeadersWriter;
+import org.springframework.security.web.server.header.CrossOriginOpenerPolicyServerHttpHeadersWriter.CrossOriginOpenerPolicy;
+import org.springframework.security.web.server.header.CrossOriginResourcePolicyServerHttpHeadersWriter;
+import org.springframework.security.web.server.header.CrossOriginResourcePolicyServerHttpHeadersWriter.CrossOriginResourcePolicy;
 import org.springframework.security.web.server.header.FeaturePolicyServerHttpHeadersWriter;
 import org.springframework.security.web.server.header.HttpHeaderWriterWebFilter;
 import org.springframework.security.web.server.header.PermissionsPolicyServerHttpHeadersWriter;
@@ -2380,10 +2386,17 @@ public class ServerHttpSecurity {
 
 		private ReferrerPolicyServerHttpHeadersWriter referrerPolicy = new ReferrerPolicyServerHttpHeadersWriter();
 
+		private CrossOriginOpenerPolicyServerHttpHeadersWriter crossOriginOpenerPolicy = new CrossOriginOpenerPolicyServerHttpHeadersWriter();
+
+		private CrossOriginEmbedderPolicyServerHttpHeadersWriter crossOriginEmbedderPolicy = new CrossOriginEmbedderPolicyServerHttpHeadersWriter();
+
+		private CrossOriginResourcePolicyServerHttpHeadersWriter crossOriginResourcePolicy = new CrossOriginResourcePolicyServerHttpHeadersWriter();
+
 		private HeaderSpec() {
 			this.writers = new ArrayList<>(Arrays.asList(this.cacheControl, this.contentTypeOptions, this.hsts,
 					this.frameOptions, this.xss, this.featurePolicy, this.permissionsPolicy, this.contentSecurityPolicy,
-					this.referrerPolicy));
+					this.referrerPolicy, this.crossOriginOpenerPolicy, this.crossOriginEmbedderPolicy,
+					this.crossOriginResourcePolicy));
 		}
 
 		/**
@@ -2595,6 +2608,84 @@ public class ServerHttpSecurity {
 			return this;
 		}
 
+		/**
+		 * Configures the <a href=
+		 * "https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cross-Origin-Opener-Policy">
+		 * Cross-Origin-Opener-Policy</a> header.
+		 * @return the {@link CrossOriginOpenerPolicySpec} to configure
+		 * @since 5.7
+		 * @see CrossOriginOpenerPolicyServerHttpHeadersWriter
+		 */
+		public CrossOriginOpenerPolicySpec crossOriginOpenerPolicy() {
+			return new CrossOriginOpenerPolicySpec();
+		}
+
+		/**
+		 * Configures the <a href=
+		 * "https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cross-Origin-Opener-Policy">
+		 * Cross-Origin-Opener-Policy</a> header.
+		 * @return the {@link HeaderSpec} to customize
+		 * @since 5.7
+		 * @see CrossOriginOpenerPolicyServerHttpHeadersWriter
+		 */
+		public HeaderSpec crossOriginOpenerPolicy(
+				Customizer<CrossOriginOpenerPolicySpec> crossOriginOpenerPolicyCustomizer) {
+			crossOriginOpenerPolicyCustomizer.customize(new CrossOriginOpenerPolicySpec());
+			return this;
+		}
+
+		/**
+		 * Configures the <a href=
+		 * "https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cross-Origin-Embedder-Policy">
+		 * Cross-Origin-Embedder-Policy</a> header.
+		 * @return the {@link CrossOriginEmbedderPolicySpec} to configure
+		 * @since 5.7
+		 * @see CrossOriginEmbedderPolicyServerHttpHeadersWriter
+		 */
+		public CrossOriginEmbedderPolicySpec crossOriginEmbedderPolicy() {
+			return new CrossOriginEmbedderPolicySpec();
+		}
+
+		/**
+		 * Configures the <a href=
+		 * "https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cross-Origin-Embedder-Policy">
+		 * Cross-Origin-Embedder-Policy</a> header.
+		 * @return the {@link HeaderSpec} to customize
+		 * @since 5.7
+		 * @see CrossOriginEmbedderPolicyServerHttpHeadersWriter
+		 */
+		public HeaderSpec crossOriginEmbedderPolicy(
+				Customizer<CrossOriginEmbedderPolicySpec> crossOriginEmbedderPolicyCustomizer) {
+			crossOriginEmbedderPolicyCustomizer.customize(new CrossOriginEmbedderPolicySpec());
+			return this;
+		}
+
+		/**
+		 * Configures the <a href=
+		 * "https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cross-Origin-Resource-Policy">
+		 * Cross-Origin-Resource-Policy</a> header.
+		 * @return the {@link CrossOriginResourcePolicySpec} to configure
+		 * @since 5.7
+		 * @see CrossOriginResourcePolicyServerHttpHeadersWriter
+		 */
+		public CrossOriginResourcePolicySpec crossOriginResourcePolicy() {
+			return new CrossOriginResourcePolicySpec();
+		}
+
+		/**
+		 * Configures the <a href=
+		 * "https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cross-Origin-Resource-Policy">
+		 * Cross-Origin-Resource-Policy</a> header.
+		 * @return the {@link HeaderSpec} to customize
+		 * @since 5.7
+		 * @see CrossOriginResourcePolicyServerHttpHeadersWriter
+		 */
+		public HeaderSpec crossOriginResourcePolicy(
+				Customizer<CrossOriginResourcePolicySpec> crossOriginResourcePolicyCustomizer) {
+			crossOriginResourcePolicyCustomizer.customize(new CrossOriginResourcePolicySpec());
+			return this;
+		}
+
 		/**
 		 * Configures cache control headers
 		 *
@@ -2910,6 +3001,99 @@ public class ServerHttpSecurity {
 
 		}
 
+		/**
+		 * Configures the Cross-Origin-Opener-Policy header
+		 *
+		 * @since 5.7
+		 */
+		public final class CrossOriginOpenerPolicySpec {
+
+			private CrossOriginOpenerPolicySpec() {
+			}
+
+			/**
+			 * Sets the value to be used in the `Cross-Origin-Opener-Policy` header
+			 * @param openerPolicy a opener policy
+			 * @return the {@link CrossOriginOpenerPolicySpec} to continue configuring
+			 */
+			public CrossOriginOpenerPolicySpec policy(CrossOriginOpenerPolicy openerPolicy) {
+				HeaderSpec.this.crossOriginOpenerPolicy.setPolicy(openerPolicy);
+				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 the Cross-Origin-Embedder-Policy header
+		 *
+		 * @since 5.7
+		 */
+		public final class CrossOriginEmbedderPolicySpec {
+
+			private CrossOriginEmbedderPolicySpec() {
+			}
+
+			/**
+			 * Sets the value to be used in the `Cross-Origin-Embedder-Policy` header
+			 * @param embedderPolicy a opener policy
+			 * @return the {@link CrossOriginEmbedderPolicySpec} to continue configuring
+			 */
+			public CrossOriginEmbedderPolicySpec policy(CrossOriginEmbedderPolicy embedderPolicy) {
+				HeaderSpec.this.crossOriginEmbedderPolicy.setPolicy(embedderPolicy);
+				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 the Cross-Origin-Resource-Policy header
+		 *
+		 * @since 5.7
+		 */
+		public final class CrossOriginResourcePolicySpec {
+
+			private CrossOriginResourcePolicySpec() {
+			}
+
+			/**
+			 * Sets the value to be used in the `Cross-Origin-Resource-Policy` header
+			 * @param resourcePolicy a opener policy
+			 * @return the {@link CrossOriginResourcePolicySpec} to continue configuring
+			 */
+			public CrossOriginResourcePolicySpec policy(CrossOriginResourcePolicy resourcePolicy) {
+				HeaderSpec.this.crossOriginResourcePolicy.setPolicy(resourcePolicy);
+				return this;
+			}
+
+			/**
+			 * Allows method chaining to continue configuring the
+			 * {@link ServerHttpSecurity}.
+			 * @return the {@link HeaderSpec} to continue configuring
+			 */
+			public HeaderSpec and() {
+				return HeaderSpec.this;
+			}
+
+		}
+
 	}
 
 	/**

+ 42 - 0
config/src/main/kotlin/org/springframework/security/config/web/server/ServerCrossOriginEmbedderPolicyDsl.kt

@@ -0,0 +1,42 @@
+/*
+ * Copyright 2002-2021 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.config.annotation.web.builders.HttpSecurity
+import org.springframework.security.web.server.header.CrossOriginEmbedderPolicyServerHttpHeadersWriter
+
+/**
+ * A Kotlin DSL to configure the [HttpSecurity] Cross-Origin-Embedder-Policy header using
+ * idiomatic Kotlin code.
+ *
+ * @author Marcus Da Coregio
+ * @since 5.7
+ * @property policy the policy to be used in the response header.
+ */
+@ServerSecurityMarker
+class ServerCrossOriginEmbedderPolicyDsl {
+
+    var policy: CrossOriginEmbedderPolicyServerHttpHeadersWriter.CrossOriginEmbedderPolicy? = null
+
+    internal fun get(): (ServerHttpSecurity.HeaderSpec.CrossOriginEmbedderPolicySpec) -> Unit {
+        return { crossOriginEmbedderPolicy ->
+            policy?.also {
+                crossOriginEmbedderPolicy.policy(policy)
+            }
+        }
+    }
+}

+ 42 - 0
config/src/main/kotlin/org/springframework/security/config/web/server/ServerCrossOriginOpenerPolicyDsl.kt

@@ -0,0 +1,42 @@
+/*
+ * Copyright 2002-2021 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.config.annotation.web.builders.HttpSecurity
+import org.springframework.security.web.server.header.CrossOriginOpenerPolicyServerHttpHeadersWriter
+
+/**
+ * A Kotlin DSL to configure the [HttpSecurity] Cross-Origin-Opener-Policy header using
+ * idiomatic Kotlin code.
+ *
+ * @author Marcus Da Coregio
+ * @since 5.7
+ * @property policy the policy to be used in the response header.
+ */
+@ServerSecurityMarker
+class ServerCrossOriginOpenerPolicyDsl {
+
+    var policy: CrossOriginOpenerPolicyServerHttpHeadersWriter.CrossOriginOpenerPolicy? = null
+
+    internal fun get(): (ServerHttpSecurity.HeaderSpec.CrossOriginOpenerPolicySpec) -> Unit {
+        return { crossOriginOpenerPolicy ->
+            policy?.also {
+                crossOriginOpenerPolicy.policy(policy)
+            }
+        }
+    }
+}

+ 42 - 0
config/src/main/kotlin/org/springframework/security/config/web/server/ServerCrossOriginResourcePolicyDsl.kt

@@ -0,0 +1,42 @@
+/*
+ * Copyright 2002-2021 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.config.annotation.web.builders.HttpSecurity
+import org.springframework.security.web.server.header.CrossOriginResourcePolicyServerHttpHeadersWriter
+
+/**
+ * A Kotlin DSL to configure the [HttpSecurity] Cross-Origin-Resource-Policy header using
+ * idiomatic Kotlin code.
+ *
+ * @author Marcus Da Coregio
+ * @since 5.7
+ * @property policy the policy to be used in the response header.
+ */
+@ServerSecurityMarker
+class ServerCrossOriginResourcePolicyDsl {
+
+    var policy: CrossOriginResourcePolicyServerHttpHeadersWriter.CrossOriginResourcePolicy? = null
+
+    internal fun get(): (ServerHttpSecurity.HeaderSpec.CrossOriginResourcePolicySpec) -> Unit {
+        return { crossOriginResourcePolicy ->
+            policy?.also {
+                crossOriginResourcePolicy.policy(policy)
+            }
+        }
+    }
+}

+ 51 - 1
config/src/main/kotlin/org/springframework/security/config/web/server/ServerHeadersDsl.kt

@@ -16,7 +16,12 @@
 
 package org.springframework.security.config.web.server
 
-import org.springframework.security.web.server.header.*
+import org.springframework.security.web.server.header.CacheControlServerHttpHeadersWriter
+import org.springframework.security.web.server.header.ContentTypeOptionsServerHttpHeadersWriter
+import org.springframework.security.web.server.header.ReferrerPolicyServerHttpHeadersWriter
+import org.springframework.security.web.server.header.StrictTransportSecurityServerHttpHeadersWriter
+import org.springframework.security.web.server.header.XFrameOptionsServerHttpHeadersWriter
+import org.springframework.security.web.server.header.XXssProtectionServerHttpHeadersWriter
 
 /**
  * A Kotlin DSL to configure [ServerHttpSecurity] headers using idiomatic Kotlin code.
@@ -35,6 +40,9 @@ class ServerHeadersDsl {
     private var referrerPolicy: ((ServerHttpSecurity.HeaderSpec.ReferrerPolicySpec) -> Unit)? = null
     private var featurePolicyDirectives: String? = null
     private var permissionsPolicy: ((ServerHttpSecurity.HeaderSpec.PermissionsPolicySpec) -> Unit)? = null
+    private var crossOriginOpenerPolicy: ((ServerHttpSecurity.HeaderSpec.CrossOriginOpenerPolicySpec) -> Unit)? = null
+    private var crossOriginEmbedderPolicy: ((ServerHttpSecurity.HeaderSpec.CrossOriginEmbedderPolicySpec) -> Unit)? = null
+    private var crossOriginResourcePolicy: ((ServerHttpSecurity.HeaderSpec.CrossOriginResourcePolicySpec) -> Unit)? = null
 
     private var disabled = false
 
@@ -157,6 +165,39 @@ class ServerHeadersDsl {
         this.permissionsPolicy = ServerPermissionsPolicyDsl().apply(permissionsPolicyConfig).get()
     }
 
+    /**
+     * Allows configuration for <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cross-Origin-Opener-Policy">
+     * Cross-Origin-Opener-Policy</a> header.
+     *
+     * @since 5.7
+     * @param crossOriginOpenerPolicyConfig the customization to apply to the header
+     */
+    fun crossOriginOpenerPolicy(crossOriginOpenerPolicyConfig: ServerCrossOriginOpenerPolicyDsl.() -> Unit) {
+        this.crossOriginOpenerPolicy = ServerCrossOriginOpenerPolicyDsl().apply(crossOriginOpenerPolicyConfig).get()
+    }
+
+    /**
+     * Allows configuration for <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cross-Origin-Embedder-Policy">
+     * Cross-Origin-Embedder-Policy</a> header.
+     *
+     * @since 5.7
+     * @param crossOriginEmbedderPolicyConfig the customization to apply to the header
+     */
+    fun crossOriginEmbedderPolicy(crossOriginEmbedderPolicyConfig: ServerCrossOriginEmbedderPolicyDsl.() -> Unit) {
+        this.crossOriginEmbedderPolicy = ServerCrossOriginEmbedderPolicyDsl().apply(crossOriginEmbedderPolicyConfig).get()
+    }
+
+    /**
+     * Allows configuration for <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cross-Origin-Resource-Policy">
+     * Cross-Origin-Resource-Policy</a> header.
+     *
+     * @since 5.7
+     * @param crossOriginResourcePolicyConfig the customization to apply to the header
+     */
+    fun crossOriginResourcePolicy(crossOriginResourcePolicyConfig: ServerCrossOriginResourcePolicyDsl.() -> Unit) {
+        this.crossOriginResourcePolicy = ServerCrossOriginResourcePolicyDsl().apply(crossOriginResourcePolicyConfig).get()
+    }
+
     /**
      * Disables HTTP response headers.
      */
@@ -194,6 +235,15 @@ class ServerHeadersDsl {
             referrerPolicy?.also {
                 headers.referrerPolicy(referrerPolicy)
             }
+            crossOriginOpenerPolicy?.also {
+                headers.crossOriginOpenerPolicy(crossOriginOpenerPolicy)
+            }
+            crossOriginEmbedderPolicy?.also {
+                headers.crossOriginEmbedderPolicy(crossOriginEmbedderPolicy)
+            }
+            crossOriginResourcePolicy?.also {
+                headers.crossOriginResourcePolicy(crossOriginResourcePolicy)
+            }
             if (disabled) {
                 headers.disable()
             }

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

@@ -42,6 +42,9 @@ class HeadersDsl {
     private var referrerPolicy: ((HeadersConfigurer<HttpSecurity>.ReferrerPolicyConfig) -> Unit)? = null
     private var featurePolicyDirectives: String? = null
     private var permissionsPolicy: ((HeadersConfigurer<HttpSecurity>.PermissionsPolicyConfig) -> Unit)? = null
+    private var crossOriginOpenerPolicy: ((HeadersConfigurer<HttpSecurity>.CrossOriginOpenerPolicyConfig) -> Unit)? = null
+    private var crossOriginEmbedderPolicy: ((HeadersConfigurer<HttpSecurity>.CrossOriginEmbedderPolicyConfig) -> Unit)? = null
+    private var crossOriginResourcePolicy: ((HeadersConfigurer<HttpSecurity>.CrossOriginResourcePolicyConfig) -> Unit)? = null
     private var disabled = false
     private var headerWriters = mutableListOf<HeaderWriter>()
 
@@ -181,6 +184,54 @@ class HeadersDsl {
         this.permissionsPolicy = PermissionsPolicyDsl().apply(permissionsPolicyConfig).get()
     }
 
+    /**
+     * Allows configuration for <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cross-Origin-Opener-Policy">
+     * Cross-Origin-Opener-Policy</a> header.
+     *
+     * <p>
+     * Calling this method automatically enables (includes) the Cross-Origin-Opener-Policy
+     * header in the response using the supplied policy.
+     * <p>
+     *
+     * @since 5.7
+     * @param crossOriginOpenerPolicyConfig the customization to apply to the header
+     */
+    fun crossOriginOpenerPolicy(crossOriginOpenerPolicyConfig: CrossOriginOpenerPolicyDsl.() -> Unit) {
+        this.crossOriginOpenerPolicy = CrossOriginOpenerPolicyDsl().apply(crossOriginOpenerPolicyConfig).get()
+    }
+
+    /**
+     * Allows configuration for <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cross-Origin-Embedder-Policy">
+     * Cross-Origin-Embedder-Policy</a> header.
+     *
+     * <p>
+     * Calling this method automatically enables (includes) the Cross-Origin-Embedder-Policy
+     * header in the response using the supplied policy.
+     * <p>
+     *
+     * @since 5.7
+     * @param crossOriginEmbedderPolicyConfig the customization to apply to the header
+     */
+    fun crossOriginEmbedderPolicy(crossOriginEmbedderPolicyConfig: CrossOriginEmbedderPolicyDsl.() -> Unit) {
+        this.crossOriginEmbedderPolicy = CrossOriginEmbedderPolicyDsl().apply(crossOriginEmbedderPolicyConfig).get()
+    }
+
+    /**
+     * Configures the <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cross-Origin-Resource-Policy">
+     * Cross-Origin-Resource-Policy</a> header.
+     *
+     * <p>
+     * Calling this method automatically enables (includes) the Cross-Origin-Resource-Policy
+     * header in the response using the supplied policy.
+     * <p>
+     *
+     * @since 5.7
+     * @param crossOriginResourcePolicyConfig the customization to apply to the header
+     */
+    fun crossOriginResourcePolicy(crossOriginResourcePolicyConfig: CrossOriginResourcePolicyDsl.() -> Unit) {
+        this.crossOriginResourcePolicy = CrossOriginResourcePolicyDsl().apply(crossOriginResourcePolicyConfig).get()
+    }
+
     /**
      * Adds a [HeaderWriter] instance.
      *
@@ -238,6 +289,15 @@ class HeadersDsl {
             permissionsPolicy?.also {
                 headers.permissionsPolicy(permissionsPolicy)
             }
+            crossOriginOpenerPolicy?.also {
+                headers.crossOriginOpenerPolicy(crossOriginOpenerPolicy)
+            }
+            crossOriginEmbedderPolicy?.also {
+                headers.crossOriginEmbedderPolicy(crossOriginEmbedderPolicy)
+            }
+            crossOriginResourcePolicy?.also {
+                headers.crossOriginResourcePolicy(crossOriginResourcePolicy)
+            }
             headerWriters.forEach { headerWriter ->
                 headers.addHeaderWriter(headerWriter)
             }

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

@@ -0,0 +1,43 @@
+/*
+ * Copyright 2002-2021 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
+import org.springframework.security.web.header.writers.CrossOriginEmbedderPolicyHeaderWriter
+
+/**
+ * A Kotlin DSL to configure the [HttpSecurity] Cross-Origin-Embedder-Policy header using
+ * idiomatic Kotlin code.
+ *
+ * @author Marcus Da Coregio
+ * @since 5.7
+ * @property policy the policy to be used in the response header.
+ */
+@HeadersSecurityMarker
+class CrossOriginEmbedderPolicyDsl {
+
+    var policy: CrossOriginEmbedderPolicyHeaderWriter.CrossOriginEmbedderPolicy? = null
+
+    internal fun get(): (HeadersConfigurer<HttpSecurity>.CrossOriginEmbedderPolicyConfig) -> Unit {
+        return { crossOriginEmbedderPolicy ->
+            policy?.also {
+                crossOriginEmbedderPolicy.policy(policy)
+            }
+        }
+    }
+}

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

@@ -0,0 +1,43 @@
+/*
+ * Copyright 2002-2021 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
+import org.springframework.security.web.header.writers.CrossOriginOpenerPolicyHeaderWriter
+
+/**
+ * A Kotlin DSL to configure the [HttpSecurity] Cross-Origin-Opener-Policy header using
+ * idiomatic Kotlin code.
+ *
+ * @author Marcus Da Coregio
+ * @since 5.7
+ * @property policy the policy to be used in the response header.
+ */
+@HeadersSecurityMarker
+class CrossOriginOpenerPolicyDsl {
+
+    var policy: CrossOriginOpenerPolicyHeaderWriter.CrossOriginOpenerPolicy? = null
+
+    internal fun get(): (HeadersConfigurer<HttpSecurity>.CrossOriginOpenerPolicyConfig) -> Unit {
+        return { crossOriginOpenerPolicy ->
+            policy?.also {
+                crossOriginOpenerPolicy.policy(policy)
+            }
+        }
+    }
+}

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

@@ -0,0 +1,43 @@
+/*
+ * Copyright 2002-2021 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
+import org.springframework.security.web.header.writers.CrossOriginResourcePolicyHeaderWriter
+
+/**
+ * A Kotlin DSL to configure the [HttpSecurity] Cross-Origin-Resource-Policy header using
+ * idiomatic Kotlin code.
+ *
+ * @author Marcus Da Coregio
+ * @since 5.7
+ * @property policy the policy to be used in the response header.
+ */
+@HeadersSecurityMarker
+class CrossOriginResourcePolicyDsl {
+
+    var policy: CrossOriginResourcePolicyHeaderWriter.CrossOriginResourcePolicy? = null
+
+    internal fun get(): (HeadersConfigurer<HttpSecurity>.CrossOriginResourcePolicyConfig) -> Unit {
+        return { crossOriginResourcePolicy ->
+            policy?.also {
+                crossOriginResourcePolicy.policy(policy)
+            }
+        }
+    }
+}

+ 22 - 1
config/src/main/resources/org/springframework/security/config/spring-security-5.7.rnc

@@ -943,7 +943,7 @@ csrf-options.attlist &=
 
 headers =
 ## Element for configuration of the HeaderWritersFilter. Enables easy setting for the X-Frame-Options, X-XSS-Protection and X-Content-Type-Options headers.
-element headers { headers-options.attlist, (cache-control? & xss-protection? & hsts? & frame-options? & content-type-options? & hpkp? & content-security-policy? & referrer-policy? & feature-policy? & permissions-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? & cross-origin-opener-policy? & cross-origin-embedder-policy? & cross-origin-resource-policy? & header*)}
 headers-options.attlist &=
 	## Specifies if the default headers should be disabled. Default false.
 	attribute defaults-disabled {xsd:token}?
@@ -1092,6 +1092,27 @@ content-type-options.attlist &=
 	## If disabled, the X-Content-Type-Options header will not be included. Default false.
 	attribute disabled {xsd:boolean}?
 
+cross-origin-opener-policy =
+	## Adds support for Cross-Origin-Opener-Policy header
+	element cross-origin-opener-policy {cross-origin-opener-policy-options.attlist,empty}
+cross-origin-opener-policy-options.attlist &=
+	## The policies for the Cross-Origin-Opener-Policy header.
+	attribute policy {"unsafe-none","same-origin","same-origin-allow-popups"}?
+
+cross-origin-embedder-policy =
+	## Adds support for Cross-Origin-Embedder-Policy header
+	element cross-origin-embedder-policy {cross-origin-embedder-policy-options.attlist,empty}
+cross-origin-embedder-policy-options.attlist &=
+	## The policies for the Cross-Origin-Embedder-Policy header.
+	attribute policy {"unsafe-none","require-corp"}?
+
+cross-origin-resource-policy =
+	## Adds support for Cross-Origin-Resource-Policy header
+	element cross-origin-resource-policy {cross-origin-resource-policy-options.attlist,empty}
+cross-origin-resource-policy-options.attlist &=
+	## The policies for the Cross-Origin-Resource-Policy header.
+	attribute policy {"cross-origin","same-origin","same-site"}?
+
 header=
 	## Add additional headers to the response.
 	element header {header.attlist}

+ 74 - 0
config/src/main/resources/org/springframework/security/config/spring-security-5.7.xsd

@@ -2768,6 +2768,9 @@
             <xs:element ref="security:referrer-policy"/>
             <xs:element ref="security:feature-policy"/>
             <xs:element ref="security:permissions-policy"/>
+            <xs:element ref="security:cross-origin-opener-policy"/>
+            <xs:element ref="security:cross-origin-embedder-policy"/>
+            <xs:element ref="security:cross-origin-resource-policy"/>
             <xs:element ref="security:header"/>
          </xs:choice>
          <xs:attributeGroup ref="security:headers-options.attlist"/>
@@ -3151,6 +3154,77 @@
          </xs:annotation>
       </xs:attribute>
   </xs:attributeGroup>
+  <xs:element name="cross-origin-opener-policy">
+      <xs:annotation>
+         <xs:documentation>Adds support for Cross-Origin-Opener-Policy header
+                </xs:documentation>
+      </xs:annotation>
+      <xs:complexType>
+         <xs:attributeGroup ref="security:cross-origin-opener-policy-options.attlist"/>
+      </xs:complexType>
+   </xs:element>
+  <xs:attributeGroup name="cross-origin-opener-policy-options.attlist">
+      <xs:attribute name="policy">
+         <xs:annotation>
+            <xs:documentation>The policies for the Cross-Origin-Opener-Policy header.
+                </xs:documentation>
+         </xs:annotation>
+         <xs:simpleType>
+            <xs:restriction base="xs:token">
+               <xs:enumeration value="unsafe-none"/>
+               <xs:enumeration value="same-origin"/>
+               <xs:enumeration value="same-origin-allow-popups"/>
+            </xs:restriction>
+         </xs:simpleType>
+      </xs:attribute>
+  </xs:attributeGroup>
+  <xs:element name="cross-origin-embedder-policy">
+      <xs:annotation>
+         <xs:documentation>Adds support for Cross-Origin-Embedder-Policy header
+                </xs:documentation>
+      </xs:annotation>
+      <xs:complexType>
+         <xs:attributeGroup ref="security:cross-origin-embedder-policy-options.attlist"/>
+      </xs:complexType>
+   </xs:element>
+  <xs:attributeGroup name="cross-origin-embedder-policy-options.attlist">
+      <xs:attribute name="policy">
+         <xs:annotation>
+            <xs:documentation>The policies for the Cross-Origin-Embedder-Policy header.
+                </xs:documentation>
+         </xs:annotation>
+         <xs:simpleType>
+            <xs:restriction base="xs:token">
+               <xs:enumeration value="unsafe-none"/>
+               <xs:enumeration value="require-corp"/>
+            </xs:restriction>
+         </xs:simpleType>
+      </xs:attribute>
+  </xs:attributeGroup>
+  <xs:element name="cross-origin-resource-policy">
+      <xs:annotation>
+         <xs:documentation>Adds support for Cross-Origin-Resource-Policy header
+                </xs:documentation>
+      </xs:annotation>
+      <xs:complexType>
+         <xs:attributeGroup ref="security:cross-origin-resource-policy-options.attlist"/>
+      </xs:complexType>
+   </xs:element>
+  <xs:attributeGroup name="cross-origin-resource-policy-options.attlist">
+      <xs:attribute name="policy">
+         <xs:annotation>
+            <xs:documentation>The policies for the Cross-Origin-Resource-Policy header.
+                </xs:documentation>
+         </xs:annotation>
+         <xs:simpleType>
+            <xs:restriction base="xs:token">
+               <xs:enumeration value="cross-origin"/>
+               <xs:enumeration value="same-origin"/>
+               <xs:enumeration value="same-site"/>
+            </xs:restriction>
+         </xs:simpleType>
+      </xs:attribute>
+  </xs:attributeGroup>
   <xs:element name="header">
       <xs:annotation>
          <xs:documentation>Add additional headers to the response.

+ 77 - 1
config/src/test/java/org/springframework/security/config/annotation/web/configurers/HeadersConfigurerTests.java

@@ -1,5 +1,5 @@
 /*
- * Copyright 2002-2019 the original author or authors.
+ * Copyright 2002-2021 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.
@@ -26,11 +26,16 @@ import org.junit.jupiter.api.extension.ExtendWith;
 
 import org.springframework.beans.factory.BeanCreationException;
 import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Bean;
 import org.springframework.security.config.annotation.web.builders.HttpSecurity;
 import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
 import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
 import org.springframework.security.config.test.SpringTestContext;
 import org.springframework.security.config.test.SpringTestContextExtension;
+import org.springframework.security.web.SecurityFilterChain;
+import org.springframework.security.web.header.writers.CrossOriginEmbedderPolicyHeaderWriter;
+import org.springframework.security.web.header.writers.CrossOriginOpenerPolicyHeaderWriter;
+import org.springframework.security.web.header.writers.CrossOriginResourcePolicyHeaderWriter;
 import org.springframework.security.web.header.writers.ReferrerPolicyHeaderWriter.ReferrerPolicy;
 import org.springframework.security.web.header.writers.frameoptions.XFrameOptionsHeaderWriter.XFrameOptionsMode;
 import org.springframework.test.web.servlet.MockMvc;
@@ -52,6 +57,7 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.
  * @author Eddú Meléndez
  * @author Vedran Pavic
  * @author Eleftheria Stein
+ * @author Marcus Da Coregio
  */
 @ExtendWith(SpringTestContextExtension.class)
 public class HeadersConfigurerTests {
@@ -514,6 +520,30 @@ public class HeadersConfigurerTests {
 		assertThat(mvcResult.getResponse().getHeaderNames()).containsExactly(HttpHeaders.STRICT_TRANSPORT_SECURITY);
 	}
 
+	@Test
+	public void getWhenCustomCrossOriginPoliciesInLambdaThenCrossOriginPolicyHeadersWithCustomValuesInResponse()
+			throws Exception {
+		this.spring.register(CrossOriginCustomPoliciesInLambdaConfig.class).autowire();
+		MvcResult mvcResult = this.mvc.perform(get("/"))
+				.andExpect(header().string(HttpHeaders.CROSS_ORIGIN_OPENER_POLICY, "same-origin"))
+				.andExpect(header().string(HttpHeaders.CROSS_ORIGIN_EMBEDDER_POLICY, "require-corp"))
+				.andExpect(header().string(HttpHeaders.CROSS_ORIGIN_RESOURCE_POLICY, "same-origin")).andReturn();
+		assertThat(mvcResult.getResponse().getHeaderNames()).containsExactly(HttpHeaders.CROSS_ORIGIN_OPENER_POLICY,
+				HttpHeaders.CROSS_ORIGIN_EMBEDDER_POLICY, HttpHeaders.CROSS_ORIGIN_RESOURCE_POLICY);
+	}
+
+	@Test
+	public void getWhenCustomCrossOriginPoliciesThenCrossOriginPolicyHeadersWithCustomValuesInResponse()
+			throws Exception {
+		this.spring.register(CrossOriginCustomPoliciesConfig.class).autowire();
+		MvcResult mvcResult = this.mvc.perform(get("/"))
+				.andExpect(header().string(HttpHeaders.CROSS_ORIGIN_OPENER_POLICY, "same-origin"))
+				.andExpect(header().string(HttpHeaders.CROSS_ORIGIN_EMBEDDER_POLICY, "require-corp"))
+				.andExpect(header().string(HttpHeaders.CROSS_ORIGIN_RESOURCE_POLICY, "same-origin")).andReturn();
+		assertThat(mvcResult.getResponse().getHeaderNames()).containsExactly(HttpHeaders.CROSS_ORIGIN_OPENER_POLICY,
+				HttpHeaders.CROSS_ORIGIN_EMBEDDER_POLICY, HttpHeaders.CROSS_ORIGIN_RESOURCE_POLICY);
+	}
+
 	@EnableWebSecurity
 	static class HeadersConfig extends WebSecurityConfigurerAdapter {
 
@@ -1146,4 +1176,50 @@ public class HeadersConfigurerTests {
 
 	}
 
+	@EnableWebSecurity
+	static class CrossOriginCustomPoliciesInLambdaConfig {
+
+		@Bean
+		SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
+			// @formatter:off
+			http.headers((headers) -> headers
+					.defaultsDisabled()
+					.crossOriginOpenerPolicy((policy) -> policy
+						.policy(CrossOriginOpenerPolicyHeaderWriter.CrossOriginOpenerPolicy.SAME_ORIGIN)
+					)
+					.crossOriginEmbedderPolicy((policy) -> policy
+						.policy(CrossOriginEmbedderPolicyHeaderWriter.CrossOriginEmbedderPolicy.REQUIRE_CORP)
+					)
+					.crossOriginResourcePolicy((policy) -> policy
+						.policy(CrossOriginResourcePolicyHeaderWriter.CrossOriginResourcePolicy.SAME_ORIGIN)
+					)
+			);
+			// @formatter:on
+			return http.build();
+		}
+
+	}
+
+	@EnableWebSecurity
+	static class CrossOriginCustomPoliciesConfig {
+
+		@Bean
+		SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
+			// @formatter:off
+			http.headers()
+					.defaultsDisabled()
+					.crossOriginOpenerPolicy()
+						.policy(CrossOriginOpenerPolicyHeaderWriter.CrossOriginOpenerPolicy.SAME_ORIGIN)
+						.and()
+					.crossOriginEmbedderPolicy()
+						.policy(CrossOriginEmbedderPolicyHeaderWriter.CrossOriginEmbedderPolicy.REQUIRE_CORP)
+						.and()
+					.crossOriginResourcePolicy()
+						.policy(CrossOriginResourcePolicyHeaderWriter.CrossOriginResourcePolicy.SAME_ORIGIN);
+			// @formatter:on
+			return http.build();
+		}
+
+	}
+
 }

+ 49 - 1
config/src/test/java/org/springframework/security/config/http/HttpHeadersConfigTests.java

@@ -1,5 +1,5 @@
 /*
- * Copyright 2002-2019 the original author or authors.
+ * Copyright 2002-2021 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.
@@ -48,6 +48,7 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.
  * @author Tim Ysewyn
  * @author Josh Cummings
  * @author Rafiullah Hamedy
+ * @author Marcus Da Coregio
  */
 @ExtendWith(SpringTestContextExtension.class)
 public class HttpHeadersConfigTests {
@@ -733,6 +734,53 @@ public class HttpHeadersConfigTests {
 		// @formatter:on
 	}
 
+	@Test
+	public void requestWhenCrossOriginOpenerPolicyWithSameOriginAllowPopupsThenRespondsWithSameOriginAllowPopups()
+			throws Exception {
+		this.spring.configLocations(this.xml("DefaultsDisabledWithCrossOriginOpenerPolicy")).autowire();
+		// @formatter:off
+		this.mvc.perform(get("/"))
+				.andExpect(status().isOk())
+				.andExpect(excludesDefaults())
+				.andExpect(header().string("Cross-Origin-Opener-Policy", "same-origin-allow-popups"));
+		// @formatter:on
+	}
+
+	@Test
+	public void requestWhenCrossOriginEmbedderPolicyWithRequireCorpThenRespondsWithRequireCorp() throws Exception {
+		this.spring.configLocations(this.xml("DefaultsDisabledWithCrossOriginEmbedderPolicy")).autowire();
+		// @formatter:off
+		this.mvc.perform(get("/"))
+				.andExpect(status().isOk())
+				.andExpect(excludesDefaults())
+				.andExpect(header().string("Cross-Origin-Embedder-Policy", "require-corp"));
+		// @formatter:on
+	}
+
+	@Test
+	public void requestWhenCrossOriginResourcePolicyWithSameOriginThenRespondsWithSameOrigin() throws Exception {
+		this.spring.configLocations(this.xml("DefaultsDisabledWithCrossOriginResourcePolicy")).autowire();
+		// @formatter:off
+		this.mvc.perform(get("/"))
+				.andExpect(status().isOk())
+				.andExpect(excludesDefaults())
+				.andExpect(header().string("Cross-Origin-Resource-Policy", "same-origin"));
+		// @formatter:on
+	}
+
+	@Test
+	public void requestWhenCrossOriginPoliciesRespondsCrossOriginPolicies() throws Exception {
+		this.spring.configLocations(this.xml("DefaultsDisabledWithCrossOriginPolicies")).autowire();
+		// @formatter:off
+		this.mvc.perform(get("/"))
+				.andExpect(status().isOk())
+				.andExpect(excludesDefaults())
+				.andExpect(header().string("Cross-Origin-Opener-Policy", "same-origin"))
+				.andExpect(header().string("Cross-Origin-Embedder-Policy", "require-corp"))
+				.andExpect(header().string("Cross-Origin-Resource-Policy", "same-origin"));
+		// @formatter:on
+	}
+
 	private static ResultMatcher includesDefaults() {
 		return includes(defaultHeaders);
 	}

+ 52 - 1
config/src/test/java/org/springframework/security/config/web/server/HeaderSpecTests.java

@@ -1,5 +1,5 @@
 /*
- * Copyright 2002-2019 the original author or authors.
+ * Copyright 2002-2021 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.
@@ -30,6 +30,9 @@ import org.springframework.http.HttpHeaders;
 import org.springframework.security.test.web.reactive.server.WebTestClientBuilder;
 import org.springframework.security.web.server.header.ContentSecurityPolicyServerHttpHeadersWriter;
 import org.springframework.security.web.server.header.ContentTypeOptionsServerHttpHeadersWriter;
+import org.springframework.security.web.server.header.CrossOriginEmbedderPolicyServerHttpHeadersWriter;
+import org.springframework.security.web.server.header.CrossOriginOpenerPolicyServerHttpHeadersWriter;
+import org.springframework.security.web.server.header.CrossOriginResourcePolicyServerHttpHeadersWriter;
 import org.springframework.security.web.server.header.FeaturePolicyServerHttpHeadersWriter;
 import org.springframework.security.web.server.header.ReferrerPolicyServerHttpHeadersWriter;
 import org.springframework.security.web.server.header.ReferrerPolicyServerHttpHeadersWriter.ReferrerPolicy;
@@ -48,6 +51,7 @@ import static org.springframework.security.config.Customizer.withDefaults;
  * @author Rob Winch
  * @author Vedran Pavic
  * @author Ankur Pathak
+ * @author Marcus Da Coregio
  * @since 5.0
  */
 public class HeaderSpecTests {
@@ -406,6 +410,53 @@ public class HeaderSpecTests {
 		assertHeaders();
 	}
 
+	@Test
+	public void headersWhenCrossOriginPoliciesCustomEnabledThenCustomCrossOriginPoliciesWritten() {
+		this.expectedHeaders.add(CrossOriginOpenerPolicyServerHttpHeadersWriter.OPENER_POLICY,
+				CrossOriginOpenerPolicyServerHttpHeadersWriter.CrossOriginOpenerPolicy.SAME_ORIGIN_ALLOW_POPUPS
+						.getPolicy());
+		this.expectedHeaders.add(CrossOriginEmbedderPolicyServerHttpHeadersWriter.EMBEDDER_POLICY,
+				CrossOriginEmbedderPolicyServerHttpHeadersWriter.CrossOriginEmbedderPolicy.REQUIRE_CORP.getPolicy());
+		this.expectedHeaders.add(CrossOriginResourcePolicyServerHttpHeadersWriter.RESOURCE_POLICY,
+				CrossOriginResourcePolicyServerHttpHeadersWriter.CrossOriginResourcePolicy.SAME_ORIGIN.getPolicy());
+		// @formatter:off
+		this.http.headers()
+				.crossOriginOpenerPolicy()
+						.policy(CrossOriginOpenerPolicyServerHttpHeadersWriter.CrossOriginOpenerPolicy.SAME_ORIGIN_ALLOW_POPUPS)
+						.and()
+				.crossOriginEmbedderPolicy()
+						.policy(CrossOriginEmbedderPolicyServerHttpHeadersWriter.CrossOriginEmbedderPolicy.REQUIRE_CORP)
+						.and()
+				.crossOriginResourcePolicy()
+						.policy(CrossOriginResourcePolicyServerHttpHeadersWriter.CrossOriginResourcePolicy.SAME_ORIGIN);
+		// @formatter:on
+		assertHeaders();
+	}
+
+	@Test
+	public void headersWhenCrossOriginPoliciesCustomEnabledInLambdaThenCustomCrossOriginPoliciesWritten() {
+		this.expectedHeaders.add(CrossOriginOpenerPolicyServerHttpHeadersWriter.OPENER_POLICY,
+				CrossOriginOpenerPolicyServerHttpHeadersWriter.CrossOriginOpenerPolicy.SAME_ORIGIN_ALLOW_POPUPS
+						.getPolicy());
+		this.expectedHeaders.add(CrossOriginEmbedderPolicyServerHttpHeadersWriter.EMBEDDER_POLICY,
+				CrossOriginEmbedderPolicyServerHttpHeadersWriter.CrossOriginEmbedderPolicy.REQUIRE_CORP.getPolicy());
+		this.expectedHeaders.add(CrossOriginResourcePolicyServerHttpHeadersWriter.RESOURCE_POLICY,
+				CrossOriginResourcePolicyServerHttpHeadersWriter.CrossOriginResourcePolicy.SAME_ORIGIN.getPolicy());
+		// @formatter:off
+		this.http.headers()
+				.crossOriginOpenerPolicy((policy) -> policy
+						.policy(CrossOriginOpenerPolicyServerHttpHeadersWriter.CrossOriginOpenerPolicy.SAME_ORIGIN_ALLOW_POPUPS)
+				)
+				.crossOriginEmbedderPolicy((policy) -> policy
+						.policy(CrossOriginEmbedderPolicyServerHttpHeadersWriter.CrossOriginEmbedderPolicy.REQUIRE_CORP)
+				)
+				.crossOriginResourcePolicy((policy) -> policy
+						.policy(CrossOriginResourcePolicyServerHttpHeadersWriter.CrossOriginResourcePolicy.SAME_ORIGIN)
+				);
+		// @formatter:on
+		assertHeaders();
+	}
+
 	private void expectHeaderNamesNotPresent(String... headerNames) {
 		for (String headerName : headerNames) {
 			this.expectedHeaders.remove(headerName);

+ 59 - 0
config/src/test/kotlin/org/springframework/security/config/web/server/ServerHeadersDslTests.kt

@@ -28,6 +28,9 @@ import org.springframework.security.config.test.SpringTestContextExtension
 import org.springframework.security.web.header.writers.frameoptions.XFrameOptionsHeaderWriter
 import org.springframework.security.web.server.SecurityWebFilterChain
 import org.springframework.security.web.server.header.ContentTypeOptionsServerHttpHeadersWriter
+import org.springframework.security.web.server.header.CrossOriginEmbedderPolicyServerHttpHeadersWriter
+import org.springframework.security.web.server.header.CrossOriginOpenerPolicyServerHttpHeadersWriter
+import org.springframework.security.web.server.header.CrossOriginResourcePolicyServerHttpHeadersWriter
 import org.springframework.security.web.server.header.StrictTransportSecurityServerHttpHeadersWriter
 import org.springframework.security.web.server.header.XFrameOptionsServerHttpHeadersWriter
 import org.springframework.security.web.server.header.XXssProtectionServerHttpHeadersWriter
@@ -133,4 +136,60 @@ class ServerHeadersDslTests {
             }
         }
     }
+
+    @Test
+    fun `request when no cross-origin policies configured then does not write cross-origin policies headers in response`() {
+        this.spring.register(CrossOriginPoliciesConfig::class.java).autowire()
+
+        this.client.get()
+                .uri("/")
+                .exchange()
+                .expectHeader().doesNotExist("Cross-Origin-Opener-Policy")
+                .expectHeader().doesNotExist("Cross-Origin-Embedder-Policy")
+                .expectHeader().doesNotExist("Cross-Origin-Resource-Policy")
+    }
+
+    @EnableWebFluxSecurity
+    @EnableWebFlux
+    open class CrossOriginPoliciesConfig {
+        @Bean
+        open fun springWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
+            return http {
+                headers { }
+            }
+        }
+    }
+
+    @Test
+    fun `request when cross-origin custom policies configured then cross-origin custom policies headers in response`() {
+        this.spring.register(CrossOriginPoliciesCustomConfig::class.java).autowire()
+
+        this.client.get()
+                .uri("/")
+                .exchange()
+                .expectHeader().valueEquals("Cross-Origin-Opener-Policy", "same-origin")
+                .expectHeader().valueEquals("Cross-Origin-Embedder-Policy", "require-corp")
+                .expectHeader().valueEquals("Cross-Origin-Resource-Policy", "same-origin")
+    }
+
+    @EnableWebFluxSecurity
+    @EnableWebFlux
+    open class CrossOriginPoliciesCustomConfig {
+        @Bean
+        open fun springWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
+            return http {
+                headers {
+                    crossOriginOpenerPolicy {
+                        policy = CrossOriginOpenerPolicyServerHttpHeadersWriter.CrossOriginOpenerPolicy.SAME_ORIGIN
+                    }
+                    crossOriginEmbedderPolicy {
+                        policy = CrossOriginEmbedderPolicyServerHttpHeadersWriter.CrossOriginEmbedderPolicy.REQUIRE_CORP
+                    }
+                    crossOriginResourcePolicy {
+                        policy = CrossOriginResourcePolicyServerHttpHeadersWriter.CrossOriginResourcePolicy.SAME_ORIGIN
+                    }
+                }
+            }
+        }
+    }
 }

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

@@ -19,13 +19,13 @@ package org.springframework.security.config.web.servlet
 import org.junit.jupiter.api.Test
 import org.junit.jupiter.api.extension.ExtendWith
 import org.springframework.beans.factory.annotation.Autowired
+import org.springframework.context.annotation.Bean
 import org.springframework.http.HttpHeaders
 import org.springframework.security.config.annotation.web.builders.HttpSecurity
 import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity
 import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter
 import org.springframework.security.config.test.SpringTestContext
 import org.springframework.security.config.test.SpringTestContextExtension
-import org.springframework.security.config.web.servlet.headers.PermissionsPolicyDsl
 import org.springframework.security.web.header.writers.StaticHeadersWriter
 import org.springframework.security.web.header.writers.frameoptions.XFrameOptionsHeaderWriter
 import org.springframework.security.web.server.header.ContentTypeOptionsServerHttpHeadersWriter

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

@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  ~ Copyright 2002-2021 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">
+			<cross-origin-embedder-policy policy="require-corp"/>
+		</headers>
+	</http>
+
+	<b:bean name="simple" class="org.springframework.security.config.http.HttpHeadersConfigTests.SimpleController"/>
+
+	<b:import resource="userservice.xml"/>
+</b:beans>

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

@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  ~ Copyright 2002-2021 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">
+			<cross-origin-opener-policy policy="same-origin-allow-popups"/>
+		</headers>
+	</http>
+
+	<b:bean name="simple" class="org.springframework.security.config.http.HttpHeadersConfigTests.SimpleController"/>
+
+	<b:import resource="userservice.xml"/>
+</b:beans>

+ 38 - 0
config/src/test/resources/org/springframework/security/config/http/HttpHeadersConfigTests-DefaultsDisabledWithCrossOriginPolicies.xml

@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  ~ Copyright 2002-2021 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">
+			<cross-origin-opener-policy policy="same-origin"/>
+			<cross-origin-embedder-policy policy="require-corp"/>
+			<cross-origin-resource-policy policy="same-origin"/>
+		</headers>
+	</http>
+
+	<b:bean name="simple" class="org.springframework.security.config.http.HttpHeadersConfigTests.SimpleController"/>
+
+	<b:import resource="userservice.xml"/>
+</b:beans>

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

@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  ~ Copyright 2002-2021 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">
+			<cross-origin-resource-policy policy="same-origin"/>
+		</headers>
+	</http>
+
+	<b:bean name="simple" class="org.springframework.security.config.http.HttpHeadersConfigTests.SimpleController"/>
+
+	<b:import resource="userservice.xml"/>
+</b:beans>

+ 20 - 0
docs/modules/ROOT/pages/features/exploits/headers.adoc

@@ -378,6 +378,26 @@ Clear-Site-Data: "cache", "cookies", "storage", "executionContexts"
 
 This is a nice clean-up action to perform on logout.
 
+[[headers-cross-origin-policies]]
+== Cross-Origin Policies
+
+[NOTE]
+====
+Refer to the relevant sections to see how to configure for both <<servlet-headers-cross-origin-policies,servlet>> and <<webflux-headers-cross-origin-policies,webflux>> based applications.
+====
+
+Spring Security provides support for some important Cross-Origin Policies headers.
+Those headers are:
+
+* https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cross-Origin-Opener-Policy[`Cross-Origin-Opener-Policy`]
+* https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cross-Origin-Embedder-Policy[`Cross-Origin-Embedder-Policy`]
+* https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cross-Origin-Resource-Policy[`Cross-Origin-Resource-Policy`]
+
+`Cross-Origin-Opener-Policy` (COOP) allows a top-level document to break the association between its window and any others in the browsing context group (e.g., between a popup and its opener), preventing any direct DOM access between them.
+
+Enabling `Cross-Origin-Embedder-Policy` (COEP) prevents a document from loading any non-same-origin resources which don't explicitly grant the document permission to be loaded.
+
+The `Cross-Origin-Resource-Policy` (CORP) header allows you to control the set of origins that are empowered to include a resource. It is a robust defense against attacks like https://meltdownattack.com[Spectre], as it allows browsers to block a given response before it enters an attacker's process.
 
 [[headers-custom]]
 == Custom Headers

+ 62 - 0
docs/modules/ROOT/pages/reactive/exploits/headers.adoc

@@ -578,3 +578,65 @@ fun webFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
 }
 ----
 ====
+
+[[webflux-headers-cross-origin-policies]]
+== Cross-Origin Policies
+
+Spring Security provides built-in support for adding some Cross-Origin policies headers, those headers are:
+
+[source]
+----
+Cross-Origin-Opener-Policy
+Cross-Origin-Embedder-Policy
+Cross-Origin-Resource-Policy
+----
+
+Spring Security does not add <<headers-cross-origin-policies,Cross-Origin Policies>> headers by default.
+The headers can be added with the following configuration:
+
+.Cross-Origin Policies
+====
+.Java
+[source,java,role="primary"]
+----
+@EnableWebFluxSecurity
+@EnableWebFlux
+public class WebSecurityConfig {
+
+    @Bean
+    SecurityWebFilterChain securityFilterChain(ServerHttpSecurity http) {
+        http.headers((headers) -> headers
+                .crossOriginOpenerPolicy(CrossOriginOpenerPolicy.SAME_ORIGIN)
+                .crossOriginEmbedderPolicy(CrossOriginEmbedderPolicy.REQUIRE_CORP)
+                .crossOriginResourcePolicy(CrossOriginResourcePolicy.SAME_ORIGIN));
+        return http.build();
+    }
+}
+----
+.Kotlin
+[source,kotlin,role="secondary"]
+----
+@EnableWebFluxSecurity
+@EnableWebFlux
+open class CrossOriginPoliciesCustomConfig {
+    @Bean
+    open fun springWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
+        return http {
+            headers {
+                crossOriginOpenerPolicy(CrossOriginOpenerPolicy.SAME_ORIGIN)
+                crossOriginEmbedderPolicy(CrossOriginEmbedderPolicy.REQUIRE_CORP)
+                crossOriginResourcePolicy(CrossOriginResourcePolicy.SAME_ORIGIN)
+            }
+        }
+    }
+}
+----
+====
+
+This configuration will write the headers with the values provided:
+[source]
+----
+Cross-Origin-Opener-Policy: same-origin
+Cross-Origin-Embedder-Policy: require-corp
+Cross-Origin-Resource-Policy: same-origin
+----

+ 66 - 0
docs/modules/ROOT/pages/servlet/appendix/namespace/http.adoc

@@ -238,6 +238,9 @@ This allows HTTPS websites to resist impersonation by attackers using mis-issued
 https://www.w3.org/TR/CSP2/[Content Security Policy (CSP)] is a mechanism that web applications can leverage to mitigate content injection vulnerabilities, such as cross-site scripting (XSS).
 ** `Referrer-Policy` - Can be set using the <<nsa-referrer-policy,referrer-policy>> element, https://www.w3.org/TR/referrer-policy/[Referrer-Policy] is a mechanism that web applications can leverage to manage the referrer field, which contains the last page the user was on.
 ** `Feature-Policy` - Can be set using the <<nsa-feature-policy,feature-policy>> element, https://wicg.github.io/feature-policy/[Feature-Policy] is a mechanism that allows web developers to selectively enable, disable, and modify the behavior of certain APIs and web features in the browser.
+** `Cross-Origin-Opener-Policy` - Can be set using the <<nsa-cross-origin-opener-policy,cross-origin-opener-policy>> element, https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cross-Origin-Opener-Policy[Cross-Origin-Opener-Policy] is a mechanism that allows you to ensure a top-level document does not share a browsing context group with cross-origin documents.
+** `Cross-Origin-Embedder-Policy` - Can be set using the <<nsa-cross-origin-embedder-policy,cross-origin-embedder-policy>> element, https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cross-Origin-Embedder-Policy[Cross-Origin-Embedder-Policy] is a mechanism that prevents a document from loading any cross-origin resources that don't explicitly grant the document permission.
+** `Cross-Origin-Resource-Policy` - Can be set using the <<nsa-cross-origin-resource-policy,cross-origin-resource-policy>> element, https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cross-Origin-Resource-Policy[Cross-Origin-Resource-Policy] is a mechanism that conveys a desire that the browser blocks no-cors cross-origin/cross-site requests to the given resource.
 
 [[nsa-headers-attributes]]
 === <headers> Attributes
@@ -269,6 +272,9 @@ The default is false (the headers are enabled).
 * <<nsa-cache-control,cache-control>>
 * <<nsa-content-security-policy,content-security-policy>>
 * <<nsa-content-type-options,content-type-options>>
+* <<nsa-cross-origin-embedder-policy,cross-origin-embedder-policy>>
+* <<nsa-cross-origin-opener-policy,cross-origin-opener-policy>>
+* <<nsa-cross-origin-resource-policy,cross-origin-resource-policy>>
 * <<nsa-feature-policy,feature-policy>>
 * <<nsa-frame-options,frame-options>>
 * <<nsa-header,header>>
@@ -584,6 +590,66 @@ Default false.
 
 
 
+[[nsa-cross-origin-embedder-policy]]
+==== <cross-origin-embedder-policy>
+When enabled adds the https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cross-Origin-Embedder-Policy[Cross-Origin-Embedder-Policy] header to the response.
+
+
+[[nsa-cross-origin-embedder-policy-attributes]]
+===== <cross-origin-embedder-policy> Attributes
+
+[[nsa-cross-origin-embedder-policy-policy]]
+* **policy**
+The policy for the `Cross-Origin-Embedder-Policy` header.
+
+[[nsa-cross-origin-embedder-policy-parents]]
+===== Parent Elements of <cross-origin-embedder-policy>
+
+
+* <<nsa-headers,headers>>
+
+
+
+[[nsa-cross-origin-opener-policy]]
+==== <cross-origin-opener-policy>
+When enabled adds the https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cross-Origin-Opener-Policy[Cross-Origin-Opener-Policy] header to the response.
+
+
+[[nsa-cross-origin-opener-policy-attributes]]
+===== <cross-origin-opener-policy> Attributes
+
+[[nsa-cross-origin-opener-policy-policy]]
+* **policy**
+The policy for the `Cross-Origin-Opener-Policy` header.
+
+[[nsa-cross-origin-opener-policy-parents]]
+===== Parent Elements of <cross-origin-opener-policy>
+
+
+* <<nsa-headers,headers>>
+
+
+
+[[nsa-cross-origin-resource-policy]]
+==== <cross-origin-resource-policy>
+When enabled adds the https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cross-Origin-Resource-Policy[Cross-Origin-Resource-Policy] header to the response.
+
+
+[[nsa-cross-origin-resource-policy-attributes]]
+===== <cross-origin-resource-policy> Attributes
+
+[[nsa-cross-origin-resource-policy-policy]]
+* **policy**
+The policy for the `Cross-Origin-Resource-Policy` header.
+
+[[nsa-cross-origin-resource-policy-parents]]
+===== Parent Elements of <cross-origin-resource-policy>
+
+
+* <<nsa-headers,headers>>
+
+
+
 [[nsa-header]]
 == <header>
 Add additional headers to the response, both the name and value need to be specified.

+ 61 - 0
docs/modules/ROOT/pages/servlet/exploits/headers.adoc

@@ -938,6 +938,67 @@ class SecurityConfig : WebSecurityConfigurerAdapter() {
 ----
 ====
 
+[[servlet-headers-cross-origin-policies]]
+== Cross-Origin Policies
+
+Spring Security provides built-in support for adding some Cross-Origin policies headers, those headers are:
+
+[source]
+----
+Cross-Origin-Opener-Policy
+Cross-Origin-Embedder-Policy
+Cross-Origin-Resource-Policy
+----
+
+Spring Security does not add <<headers-cross-origin-policies,Cross-Origin Policies>> headers by default.
+The headers can be added with the following configuration:
+
+.Cross-Origin Policies
+====
+.Java
+[source,java,role="primary"]
+----
+@EnableWebSecurity
+public class WebSecurityConfig {
+
+    @Bean
+    SecurityFilterChain securityFilterChain(HttpSecurity http) {
+        http.headers((headers) -> headers
+                .crossOriginOpenerPolicy(CrossOriginOpenerPolicy.SAME_ORIGIN)
+                .crossOriginEmbedderPolicy(CrossOriginEmbedderPolicy.REQUIRE_CORP)
+                .crossOriginResourcePolicy(CrossOriginResourcePolicy.SAME_ORIGIN)));
+        return http.build();
+    }
+}
+----
+.Kotlin
+[source,kotlin,role="secondary"]
+----
+@EnableWebSecurity
+open class CrossOriginPoliciesConfig {
+    @Bean
+    open fun securityFilterChain(http: HttpSecurity): SecurityFilterChain {
+        http {
+            headers {
+                crossOriginOpenerPolicy(CrossOriginOpenerPolicy.SAME_ORIGIN)
+                crossOriginEmbedderPolicy(CrossOriginEmbedderPolicy.REQUIRE_CORP)
+                crossOriginResourcePolicy(CrossOriginResourcePolicy.SAME_ORIGIN)
+            }
+        }
+        return http.build()
+    }
+}
+----
+====
+
+This configuration will write the headers with the values provided:
+[source]
+----
+Cross-Origin-Opener-Policy: same-origin
+Cross-Origin-Embedder-Policy: require-corp
+Cross-Origin-Resource-Policy: same-origin
+----
+
 [[servlet-headers-custom]]
 == Custom Headers
 Spring Security has mechanisms to make it convenient to add the more common security headers to your application.

+ 84 - 0
web/src/main/java/org/springframework/security/web/header/writers/CrossOriginEmbedderPolicyHeaderWriter.java

@@ -0,0 +1,84 @@
+/*
+ * Copyright 2002-2021 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;
+
+/**
+ * Inserts Cross-Origin-Embedder-Policy header.
+ *
+ * @author Marcus Da Coregio
+ * @since 5.7
+ * @see <a href=
+ * "https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cross-Origin-Embedder-Policy">
+ * Cross-Origin-Embedder-Policy</a>
+ */
+public final class CrossOriginEmbedderPolicyHeaderWriter implements HeaderWriter {
+
+	private static final String EMBEDDER_POLICY = "Cross-Origin-Embedder-Policy";
+
+	private CrossOriginEmbedderPolicy policy;
+
+	/**
+	 * Sets the {@link CrossOriginEmbedderPolicy} value to be used in the
+	 * {@code Cross-Origin-Embedder-Policy} header
+	 * @param embedderPolicy the {@link CrossOriginEmbedderPolicy} to use
+	 */
+	public void setPolicy(CrossOriginEmbedderPolicy embedderPolicy) {
+		Assert.notNull(embedderPolicy, "embedderPolicy cannot be null");
+		this.policy = embedderPolicy;
+	}
+
+	@Override
+	public void writeHeaders(HttpServletRequest request, HttpServletResponse response) {
+		if (this.policy != null && !response.containsHeader(EMBEDDER_POLICY)) {
+			response.addHeader(EMBEDDER_POLICY, this.policy.getPolicy());
+		}
+	}
+
+	public enum CrossOriginEmbedderPolicy {
+
+		UNSAFE_NONE("unsafe-none"),
+
+		REQUIRE_CORP("require-corp");
+
+		private final String policy;
+
+		CrossOriginEmbedderPolicy(String policy) {
+			this.policy = policy;
+		}
+
+		public String getPolicy() {
+			return this.policy;
+		}
+
+		public static CrossOriginEmbedderPolicy from(String embedderPolicy) {
+			for (CrossOriginEmbedderPolicy policy : values()) {
+				if (policy.getPolicy().equals(embedderPolicy)) {
+					return policy;
+				}
+			}
+			return null;
+		}
+
+	}
+
+}

+ 86 - 0
web/src/main/java/org/springframework/security/web/header/writers/CrossOriginOpenerPolicyHeaderWriter.java

@@ -0,0 +1,86 @@
+/*
+ * Copyright 2002-2021 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;
+
+/**
+ * Inserts the Cross-Origin-Opener-Policy header
+ *
+ * @author Marcus Da Coregio
+ * @since 5.7
+ * @see <a href=
+ * "https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cross-Origin-Opener-Policy">
+ * Cross-Origin-Opener-Policy</a>
+ */
+public final class CrossOriginOpenerPolicyHeaderWriter implements HeaderWriter {
+
+	private static final String OPENER_POLICY = "Cross-Origin-Opener-Policy";
+
+	private CrossOriginOpenerPolicy policy;
+
+	/**
+	 * Sets the {@link CrossOriginOpenerPolicy} value to be used in the
+	 * {@code Cross-Origin-Opener-Policy} header
+	 * @param openerPolicy the {@link CrossOriginOpenerPolicy} to use
+	 */
+	public void setPolicy(CrossOriginOpenerPolicy openerPolicy) {
+		Assert.notNull(openerPolicy, "openerPolicy cannot be null");
+		this.policy = openerPolicy;
+	}
+
+	@Override
+	public void writeHeaders(HttpServletRequest request, HttpServletResponse response) {
+		if (this.policy != null && !response.containsHeader(OPENER_POLICY)) {
+			response.addHeader(OPENER_POLICY, this.policy.getPolicy());
+		}
+	}
+
+	public enum CrossOriginOpenerPolicy {
+
+		UNSAFE_NONE("unsafe-none"),
+
+		SAME_ORIGIN_ALLOW_POPUPS("same-origin-allow-popups"),
+
+		SAME_ORIGIN("same-origin");
+
+		private final String policy;
+
+		CrossOriginOpenerPolicy(String policy) {
+			this.policy = policy;
+		}
+
+		public String getPolicy() {
+			return this.policy;
+		}
+
+		public static CrossOriginOpenerPolicy from(String openerPolicy) {
+			for (CrossOriginOpenerPolicy policy : values()) {
+				if (policy.getPolicy().equals(openerPolicy)) {
+					return policy;
+				}
+			}
+			return null;
+		}
+
+	}
+
+}

+ 86 - 0
web/src/main/java/org/springframework/security/web/header/writers/CrossOriginResourcePolicyHeaderWriter.java

@@ -0,0 +1,86 @@
+/*
+ * Copyright 2002-2021 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;
+
+/**
+ * Inserts Cross-Origin-Resource-Policy header
+ *
+ * @author Marcus Da Coregio
+ * @since 5.7
+ * @see <a href=
+ * "https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cross-Origin-Resource-Policy">
+ * Cross-Origin-Resource-Policy</a>
+ */
+public final class CrossOriginResourcePolicyHeaderWriter implements HeaderWriter {
+
+	private static final String RESOURCE_POLICY = "Cross-Origin-Resource-Policy";
+
+	private CrossOriginResourcePolicy policy;
+
+	/**
+	 * Sets the {@link CrossOriginResourcePolicy} value to be used in the
+	 * {@code Cross-Origin-Resource-Policy} header
+	 * @param resourcePolicy the {@link CrossOriginResourcePolicy} to use
+	 */
+	public void setPolicy(CrossOriginResourcePolicy resourcePolicy) {
+		Assert.notNull(resourcePolicy, "resourcePolicy cannot be null");
+		this.policy = resourcePolicy;
+	}
+
+	@Override
+	public void writeHeaders(HttpServletRequest request, HttpServletResponse response) {
+		if (this.policy != null && !response.containsHeader(RESOURCE_POLICY)) {
+			response.addHeader(RESOURCE_POLICY, this.policy.getPolicy());
+		}
+	}
+
+	public enum CrossOriginResourcePolicy {
+
+		SAME_SITE("same-site"),
+
+		SAME_ORIGIN("same-origin"),
+
+		CROSS_ORIGIN("cross-origin");
+
+		private final String policy;
+
+		CrossOriginResourcePolicy(String policy) {
+			this.policy = policy;
+		}
+
+		public String getPolicy() {
+			return this.policy;
+		}
+
+		public static CrossOriginResourcePolicy from(String resourcePolicy) {
+			for (CrossOriginResourcePolicy policy : values()) {
+				if (policy.getPolicy().equals(resourcePolicy)) {
+					return policy;
+				}
+			}
+			return null;
+		}
+
+	}
+
+}

+ 78 - 0
web/src/main/java/org/springframework/security/web/server/header/CrossOriginEmbedderPolicyServerHttpHeadersWriter.java

@@ -0,0 +1,78 @@
+/*
+ * Copyright 2002-2021 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.util.Assert;
+import org.springframework.web.server.ServerWebExchange;
+
+/**
+ * Inserts Cross-Origin-Embedder-Policy headers.
+ *
+ * @author Marcus Da Coregio
+ * @since 5.7
+ * @see <a href=
+ * "https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cross-Origin-Embedder-Policy">
+ * Cross-Origin-Embedder-Policy</a>
+ */
+public final class CrossOriginEmbedderPolicyServerHttpHeadersWriter implements ServerHttpHeadersWriter {
+
+	public static final String EMBEDDER_POLICY = "Cross-Origin-Embedder-Policy";
+
+	private ServerHttpHeadersWriter delegate;
+
+	/**
+	 * Sets the {@link CrossOriginEmbedderPolicy} value to be used in the
+	 * {@code Cross-Origin-Embedder-Policy} header
+	 * @param embedderPolicy the {@link CrossOriginEmbedderPolicy} to use
+	 */
+	public void setPolicy(CrossOriginEmbedderPolicy embedderPolicy) {
+		Assert.notNull(embedderPolicy, "embedderPolicy cannot be null");
+		this.delegate = createDelegate(embedderPolicy);
+	}
+
+	@Override
+	public Mono<Void> writeHttpHeaders(ServerWebExchange exchange) {
+		return (this.delegate != null) ? this.delegate.writeHttpHeaders(exchange) : Mono.empty();
+	}
+
+	private static ServerHttpHeadersWriter createDelegate(CrossOriginEmbedderPolicy embedderPolicy) {
+		StaticServerHttpHeadersWriter.Builder builder = StaticServerHttpHeadersWriter.builder();
+		builder.header(EMBEDDER_POLICY, embedderPolicy.getPolicy());
+		return builder.build();
+	}
+
+	public enum CrossOriginEmbedderPolicy {
+
+		UNSAFE_NONE("unsafe-none"),
+
+		REQUIRE_CORP("require-corp");
+
+		private final String policy;
+
+		CrossOriginEmbedderPolicy(String policy) {
+			this.policy = policy;
+		}
+
+		public String getPolicy() {
+			return this.policy;
+		}
+
+	}
+
+}

+ 80 - 0
web/src/main/java/org/springframework/security/web/server/header/CrossOriginOpenerPolicyServerHttpHeadersWriter.java

@@ -0,0 +1,80 @@
+/*
+ * Copyright 2002-2021 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.util.Assert;
+import org.springframework.web.server.ServerWebExchange;
+
+/**
+ * Inserts Cross-Origin-Opener-Policy header.
+ *
+ * @author Marcus Da Coregio
+ * @since 5.7
+ * @see <a href=
+ * "https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cross-Origin-Opener-Policy">
+ * Cross-Origin-Opener-Policy</a>
+ */
+public final class CrossOriginOpenerPolicyServerHttpHeadersWriter implements ServerHttpHeadersWriter {
+
+	public static final String OPENER_POLICY = "Cross-Origin-Opener-Policy";
+
+	private ServerHttpHeadersWriter delegate;
+
+	/**
+	 * Sets the {@link CrossOriginOpenerPolicy} value to be used in the
+	 * {@code Cross-Origin-Opener-Policy} header
+	 * @param openerPolicy the {@link CrossOriginOpenerPolicy} to use
+	 */
+	public void setPolicy(CrossOriginOpenerPolicy openerPolicy) {
+		Assert.notNull(openerPolicy, "openerPolicy cannot be null");
+		this.delegate = createDelegate(openerPolicy);
+	}
+
+	@Override
+	public Mono<Void> writeHttpHeaders(ServerWebExchange exchange) {
+		return (this.delegate != null) ? this.delegate.writeHttpHeaders(exchange) : Mono.empty();
+	}
+
+	private static ServerHttpHeadersWriter createDelegate(CrossOriginOpenerPolicy openerPolicy) {
+		StaticServerHttpHeadersWriter.Builder builder = StaticServerHttpHeadersWriter.builder();
+		builder.header(OPENER_POLICY, openerPolicy.getPolicy());
+		return builder.build();
+	}
+
+	public enum CrossOriginOpenerPolicy {
+
+		UNSAFE_NONE("unsafe-none"),
+
+		SAME_ORIGIN_ALLOW_POPUPS("same-origin-allow-popups"),
+
+		SAME_ORIGIN("same-origin");
+
+		private final String policy;
+
+		CrossOriginOpenerPolicy(String policy) {
+			this.policy = policy;
+		}
+
+		public String getPolicy() {
+			return this.policy;
+		}
+
+	}
+
+}

+ 80 - 0
web/src/main/java/org/springframework/security/web/server/header/CrossOriginResourcePolicyServerHttpHeadersWriter.java

@@ -0,0 +1,80 @@
+/*
+ * Copyright 2002-2021 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.util.Assert;
+import org.springframework.web.server.ServerWebExchange;
+
+/**
+ * Inserts Cross-Origin-Resource-Policy headers.
+ *
+ * @author Marcus Da Coregio
+ * @since 5.7
+ * @see <a href=
+ * "https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cross-Origin-Resource-Policy">
+ * Cross-Origin-Resource-Policy</a>
+ */
+public final class CrossOriginResourcePolicyServerHttpHeadersWriter implements ServerHttpHeadersWriter {
+
+	public static final String RESOURCE_POLICY = "Cross-Origin-Resource-Policy";
+
+	private ServerHttpHeadersWriter delegate;
+
+	/**
+	 * Sets the {@link CrossOriginResourcePolicy} value to be used in the
+	 * {@code Cross-Origin-Embedder-Policy} header
+	 * @param resourcePolicy the {@link CrossOriginResourcePolicy} to use
+	 */
+	public void setPolicy(CrossOriginResourcePolicy resourcePolicy) {
+		Assert.notNull(resourcePolicy, "resourcePolicy cannot be null");
+		this.delegate = createDelegate(resourcePolicy);
+	}
+
+	@Override
+	public Mono<Void> writeHttpHeaders(ServerWebExchange exchange) {
+		return (this.delegate != null) ? this.delegate.writeHttpHeaders(exchange) : Mono.empty();
+	}
+
+	private static ServerHttpHeadersWriter createDelegate(CrossOriginResourcePolicy resourcePolicy) {
+		StaticServerHttpHeadersWriter.Builder builder = StaticServerHttpHeadersWriter.builder();
+		builder.header(RESOURCE_POLICY, resourcePolicy.getPolicy());
+		return builder.build();
+	}
+
+	public enum CrossOriginResourcePolicy {
+
+		SAME_SITE("same-site"),
+
+		SAME_ORIGIN("same-origin"),
+
+		CROSS_ORIGIN("cross-origin");
+
+		private final String policy;
+
+		CrossOriginResourcePolicy(String policy) {
+			this.policy = policy;
+		}
+
+		public String getPolicy() {
+			return this.policy;
+		}
+
+	}
+
+}

+ 80 - 0
web/src/test/java/org/springframework/security/web/header/writers/CrossOriginEmbedderPolicyHeaderWriterTests.java

@@ -0,0 +1,80 @@
+/*
+ * Copyright 2002-2021 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.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.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;
+
+class CrossOriginEmbedderPolicyHeaderWriterTests {
+
+	private static final String EMBEDDER_HEADER_NAME = "Cross-Origin-Embedder-Policy";
+
+	private CrossOriginEmbedderPolicyHeaderWriter writer;
+
+	private MockHttpServletRequest request;
+
+	private MockHttpServletResponse response;
+
+	@BeforeEach
+	void setup() {
+		this.writer = new CrossOriginEmbedderPolicyHeaderWriter();
+		this.request = new MockHttpServletRequest();
+		this.response = new MockHttpServletResponse();
+	}
+
+	@Test
+	void setEmbedderPolicyWhenNullEmbedderPolicyThenThrowsIllegalArgument() {
+		assertThatIllegalArgumentException().isThrownBy(() -> this.writer.setPolicy(null))
+				.withMessage("embedderPolicy cannot be null");
+	}
+
+	@Test
+	void writeHeadersWhenDefaultValuesThenDontWriteHeaders() {
+		this.writer.writeHeaders(this.request, this.response);
+		assertThat(this.response.getHeaderNames()).hasSize(0);
+	}
+
+	@Test
+	void writeHeadersWhenResponseHeaderExistsThenDontOverride() {
+		this.response.addHeader(EMBEDDER_HEADER_NAME, "require-corp");
+		this.writer.setPolicy(CrossOriginEmbedderPolicyHeaderWriter.CrossOriginEmbedderPolicy.UNSAFE_NONE);
+		this.writer.writeHeaders(this.request, this.response);
+		assertThat(this.response.getHeader(EMBEDDER_HEADER_NAME)).isEqualTo("require-corp");
+	}
+
+	@Test
+	void writeHeadersWhenSetHeaderValuesThenWrites() {
+		this.writer.setPolicy(CrossOriginEmbedderPolicyHeaderWriter.CrossOriginEmbedderPolicy.REQUIRE_CORP);
+		this.writer.writeHeaders(this.request, this.response);
+		assertThat(this.response.getHeader(EMBEDDER_HEADER_NAME)).isEqualTo("require-corp");
+	}
+
+	@Test
+	void writeHeadersWhenSetEmbedderPolicyThenWritesEmbedderPolicy() {
+		this.writer.setPolicy(CrossOriginEmbedderPolicyHeaderWriter.CrossOriginEmbedderPolicy.UNSAFE_NONE);
+		this.writer.writeHeaders(this.request, this.response);
+		assertThat(this.response.getHeaderNames()).hasSize(1);
+		assertThat(this.response.getHeader(EMBEDDER_HEADER_NAME)).isEqualTo("unsafe-none");
+	}
+
+}

+ 80 - 0
web/src/test/java/org/springframework/security/web/header/writers/CrossOriginOpenerPolicyHeaderWriterTests.java

@@ -0,0 +1,80 @@
+/*
+ * Copyright 2002-2021 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.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.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;
+
+class CrossOriginOpenerPolicyHeaderWriterTests {
+
+	private static final String OPENER_HEADER_NAME = "Cross-Origin-Opener-Policy";
+
+	private CrossOriginOpenerPolicyHeaderWriter writer;
+
+	private MockHttpServletRequest request;
+
+	private MockHttpServletResponse response;
+
+	@BeforeEach
+	void setup() {
+		this.writer = new CrossOriginOpenerPolicyHeaderWriter();
+		this.request = new MockHttpServletRequest();
+		this.response = new MockHttpServletResponse();
+	}
+
+	@Test
+	void setOpenerPolicyWhenNullOpenerPolicyThenThrowsIllegalArgument() {
+		assertThatIllegalArgumentException().isThrownBy(() -> this.writer.setPolicy(null))
+				.withMessage("openerPolicy cannot be null");
+	}
+
+	@Test
+	void writeHeadersWhenDefaultValuesThenDontWriteHeaders() {
+		this.writer.writeHeaders(this.request, this.response);
+		assertThat(this.response.getHeaderNames()).hasSize(0);
+	}
+
+	@Test
+	void writeHeadersWhenResponseHeaderExistsThenDontOverride() {
+		this.response.addHeader(OPENER_HEADER_NAME, "same-origin");
+		this.writer.setPolicy(CrossOriginOpenerPolicyHeaderWriter.CrossOriginOpenerPolicy.SAME_ORIGIN_ALLOW_POPUPS);
+		this.writer.writeHeaders(this.request, this.response);
+		assertThat(this.response.getHeader(OPENER_HEADER_NAME)).isEqualTo("same-origin");
+	}
+
+	@Test
+	void writeHeadersWhenSetHeaderValuesThenWrites() {
+		this.writer.setPolicy(CrossOriginOpenerPolicyHeaderWriter.CrossOriginOpenerPolicy.SAME_ORIGIN_ALLOW_POPUPS);
+		this.writer.writeHeaders(this.request, this.response);
+		assertThat(this.response.getHeader(OPENER_HEADER_NAME)).isEqualTo("same-origin-allow-popups");
+	}
+
+	@Test
+	void writeHeadersWhenSetOpenerPolicyThenWritesOpenerPolicy() {
+		this.writer.setPolicy(CrossOriginOpenerPolicyHeaderWriter.CrossOriginOpenerPolicy.SAME_ORIGIN_ALLOW_POPUPS);
+		this.writer.writeHeaders(this.request, this.response);
+		assertThat(this.response.getHeaderNames()).hasSize(1);
+		assertThat(this.response.getHeader(OPENER_HEADER_NAME)).isEqualTo("same-origin-allow-popups");
+	}
+
+}

+ 80 - 0
web/src/test/java/org/springframework/security/web/header/writers/CrossOriginResourcePolicyHeaderWriterTests.java

@@ -0,0 +1,80 @@
+/*
+ * Copyright 2002-2021 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.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.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;
+
+class CrossOriginResourcePolicyHeaderWriterTests {
+
+	private static final String RESOURCE_HEADER_NAME = "Cross-Origin-Resource-Policy";
+
+	private CrossOriginResourcePolicyHeaderWriter writer;
+
+	private MockHttpServletRequest request;
+
+	private MockHttpServletResponse response;
+
+	@BeforeEach
+	void setup() {
+		this.writer = new CrossOriginResourcePolicyHeaderWriter();
+		this.request = new MockHttpServletRequest();
+		this.response = new MockHttpServletResponse();
+	}
+
+	@Test
+	void setResourcePolicyWhenNullThenThrowsIllegalArgument() {
+		assertThatIllegalArgumentException().isThrownBy(() -> this.writer.setPolicy(null))
+				.withMessage("resourcePolicy cannot be null");
+	}
+
+	@Test
+	void writeHeadersWhenDefaultValuesThenDontWriteHeaders() {
+		this.writer.writeHeaders(this.request, this.response);
+		assertThat(this.response.getHeaderNames()).hasSize(0);
+	}
+
+	@Test
+	void writeHeadersWhenResponseHeaderExistsThenDontOverride() {
+		this.response.addHeader(RESOURCE_HEADER_NAME, "same-site");
+		this.writer.setPolicy(CrossOriginResourcePolicyHeaderWriter.CrossOriginResourcePolicy.CROSS_ORIGIN);
+		this.writer.writeHeaders(this.request, this.response);
+		assertThat(this.response.getHeader(RESOURCE_HEADER_NAME)).isEqualTo("same-site");
+	}
+
+	@Test
+	void writeHeadersWhenSetHeaderValuesThenWrites() {
+		this.writer.setPolicy(CrossOriginResourcePolicyHeaderWriter.CrossOriginResourcePolicy.SAME_ORIGIN);
+		this.writer.writeHeaders(this.request, this.response);
+		assertThat(this.response.getHeader(RESOURCE_HEADER_NAME)).isEqualTo("same-origin");
+	}
+
+	@Test
+	void writeHeadersWhenSetResourcePolicyThenWritesResourcePolicy() {
+		this.writer.setPolicy(CrossOriginResourcePolicyHeaderWriter.CrossOriginResourcePolicy.SAME_SITE);
+		this.writer.writeHeaders(this.request, this.response);
+		assertThat(this.response.getHeaderNames()).hasSize(1);
+		assertThat(this.response.getHeader(RESOURCE_HEADER_NAME)).isEqualTo("same-site");
+	}
+
+}

+ 76 - 0
web/src/test/java/org/springframework/security/web/server/header/CrossOriginEmbedderPolicyServerHttpHeadersWriterTests.java

@@ -0,0 +1,76 @@
+/*
+ * Copyright 2002-2021 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.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.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;
+import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
+
+class CrossOriginEmbedderPolicyServerHttpHeadersWriterTests {
+
+	private ServerWebExchange exchange;
+
+	private CrossOriginEmbedderPolicyServerHttpHeadersWriter writer;
+
+	@BeforeEach
+	void setup() {
+		this.exchange = MockServerWebExchange.from(MockServerHttpRequest.get("/"));
+		this.writer = new CrossOriginEmbedderPolicyServerHttpHeadersWriter();
+	}
+
+	@Test
+	void setEmbedderPolicyWhenNullEmbedderPolicyThenThrowsIllegalArgument() {
+		assertThatIllegalArgumentException().isThrownBy(() -> this.writer.setPolicy(null))
+				.withMessage("embedderPolicy cannot be null");
+	}
+
+	@Test
+	void writeHeadersWhenNoValuesThenDoesNotWriteHeaders() {
+		this.writer.writeHttpHeaders(this.exchange);
+		HttpHeaders headers = this.exchange.getResponse().getHeaders();
+		assertThat(headers).isEmpty();
+	}
+
+	@Test
+	void writeHeadersWhenResponseHeaderExistsThenDontOverride() {
+		this.exchange.getResponse().getHeaders().add(CrossOriginEmbedderPolicyServerHttpHeadersWriter.EMBEDDER_POLICY,
+				"require-corp");
+		this.writer.writeHttpHeaders(this.exchange);
+		HttpHeaders headers = this.exchange.getResponse().getHeaders();
+		assertThat(headers).hasSize(1);
+		assertThat(headers.get(CrossOriginEmbedderPolicyServerHttpHeadersWriter.EMBEDDER_POLICY))
+				.containsOnly("require-corp");
+	}
+
+	@Test
+	void writeHeadersWhenSetHeaderValuesThenWrites() {
+		this.writer.setPolicy(CrossOriginEmbedderPolicyServerHttpHeadersWriter.CrossOriginEmbedderPolicy.REQUIRE_CORP);
+		this.writer.writeHttpHeaders(this.exchange);
+		HttpHeaders headers = this.exchange.getResponse().getHeaders();
+		assertThat(headers).hasSize(1);
+		assertThat(headers.get(CrossOriginEmbedderPolicyServerHttpHeadersWriter.EMBEDDER_POLICY))
+				.containsOnly("require-corp");
+	}
+
+}

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

@@ -0,0 +1,77 @@
+/*
+ * Copyright 2002-2021 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.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.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;
+import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
+
+class CrossOriginOpenerPolicyServerHttpHeadersWriterTests {
+
+	private ServerWebExchange exchange;
+
+	private CrossOriginOpenerPolicyServerHttpHeadersWriter writer;
+
+	@BeforeEach
+	void setup() {
+		this.exchange = MockServerWebExchange.from(MockServerHttpRequest.get("/"));
+		this.writer = new CrossOriginOpenerPolicyServerHttpHeadersWriter();
+	}
+
+	@Test
+	void setOpenerPolicyWhenNullOpenerPolicyThenThrowsIllegalArgument() {
+		assertThatIllegalArgumentException().isThrownBy(() -> this.writer.setPolicy(null))
+				.withMessage("openerPolicy cannot be null");
+	}
+
+	@Test
+	void writeHeadersWhenNoValuesThenDoesNotWriteHeaders() {
+		this.writer.writeHttpHeaders(this.exchange);
+		HttpHeaders headers = this.exchange.getResponse().getHeaders();
+		assertThat(headers).isEmpty();
+	}
+
+	@Test
+	void writeHeadersWhenResponseHeaderExistsThenDontOverride() {
+		this.exchange.getResponse().getHeaders().add(CrossOriginOpenerPolicyServerHttpHeadersWriter.OPENER_POLICY,
+				"same-origin");
+		this.writer.writeHttpHeaders(this.exchange);
+		HttpHeaders headers = this.exchange.getResponse().getHeaders();
+		assertThat(headers).hasSize(1);
+		assertThat(headers.get(CrossOriginOpenerPolicyServerHttpHeadersWriter.OPENER_POLICY))
+				.containsOnly("same-origin");
+	}
+
+	@Test
+	void writeHeadersWhenSetHeaderValuesThenWrites() {
+		this.writer.setPolicy(
+				CrossOriginOpenerPolicyServerHttpHeadersWriter.CrossOriginOpenerPolicy.SAME_ORIGIN_ALLOW_POPUPS);
+		this.writer.writeHttpHeaders(this.exchange);
+		HttpHeaders headers = this.exchange.getResponse().getHeaders();
+		assertThat(headers).hasSize(1);
+		assertThat(headers.get(CrossOriginOpenerPolicyServerHttpHeadersWriter.OPENER_POLICY))
+				.containsOnly("same-origin-allow-popups");
+	}
+
+}

+ 76 - 0
web/src/test/java/org/springframework/security/web/server/header/CrossOriginResourcePolicyServerHttpHeadersWriterTests.java

@@ -0,0 +1,76 @@
+/*
+ * Copyright 2002-2021 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.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.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;
+import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
+
+class CrossOriginResourcePolicyServerHttpHeadersWriterTests {
+
+	private ServerWebExchange exchange;
+
+	private CrossOriginResourcePolicyServerHttpHeadersWriter writer;
+
+	@BeforeEach
+	void setup() {
+		this.exchange = MockServerWebExchange.from(MockServerHttpRequest.get("/"));
+		this.writer = new CrossOriginResourcePolicyServerHttpHeadersWriter();
+	}
+
+	@Test
+	void setResourcePolicyWhenNullThenThrowsIllegalArgument() {
+		assertThatIllegalArgumentException().isThrownBy(() -> this.writer.setPolicy(null))
+				.withMessage("resourcePolicy cannot be null");
+	}
+
+	@Test
+	void writeHeadersWhenNoValuesThenDoesNotWriteHeaders() {
+		this.writer.writeHttpHeaders(this.exchange);
+		HttpHeaders headers = this.exchange.getResponse().getHeaders();
+		assertThat(headers).isEmpty();
+	}
+
+	@Test
+	void writeHeadersWhenResponseHeaderExistsThenDontOverride() {
+		this.exchange.getResponse().getHeaders().add(CrossOriginResourcePolicyServerHttpHeadersWriter.RESOURCE_POLICY,
+				"same-origin");
+		this.writer.writeHttpHeaders(this.exchange);
+		HttpHeaders headers = this.exchange.getResponse().getHeaders();
+		assertThat(headers).hasSize(1);
+		assertThat(headers.get(CrossOriginResourcePolicyServerHttpHeadersWriter.RESOURCE_POLICY))
+				.containsOnly("same-origin");
+	}
+
+	@Test
+	void writeHeadersWhenSetHeaderValuesThenWrites() {
+		this.writer.setPolicy(CrossOriginResourcePolicyServerHttpHeadersWriter.CrossOriginResourcePolicy.SAME_ORIGIN);
+		this.writer.writeHttpHeaders(this.exchange);
+		HttpHeaders headers = this.exchange.getResponse().getHeaders();
+		assertThat(headers).hasSize(1);
+		assertThat(headers.get(CrossOriginResourcePolicyServerHttpHeadersWriter.RESOURCE_POLICY))
+				.containsOnly("same-origin");
+	}
+
+}