Skip to content

Commit aa3ee9d

Browse files
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
1 parent 7ec3b55 commit aa3ee9d

File tree

38 files changed

+2513
-9
lines changed

38 files changed

+2513
-9
lines changed

config/src/main/java/org/springframework/security/config/annotation/web/configurers/HeadersConfigurer.java

Lines changed: 228 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2019 the original author or authors.
2+
* Copyright 2002-2021 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -31,6 +31,9 @@
3131
import org.springframework.security.web.header.HeaderWriterFilter;
3232
import org.springframework.security.web.header.writers.CacheControlHeadersWriter;
3333
import org.springframework.security.web.header.writers.ContentSecurityPolicyHeaderWriter;
34+
import org.springframework.security.web.header.writers.CrossOriginEmbedderPolicyHeaderWriter;
35+
import org.springframework.security.web.header.writers.CrossOriginOpenerPolicyHeaderWriter;
36+
import org.springframework.security.web.header.writers.CrossOriginResourcePolicyHeaderWriter;
3437
import org.springframework.security.web.header.writers.FeaturePolicyHeaderWriter;
3538
import org.springframework.security.web.header.writers.HpkpHeaderWriter;
3639
import org.springframework.security.web.header.writers.HstsHeaderWriter;
@@ -97,6 +100,12 @@ public class HeadersConfigurer<H extends HttpSecurityBuilder<H>>
97100

98101
private final PermissionsPolicyConfig permissionsPolicy = new PermissionsPolicyConfig();
99102

103+
private final CrossOriginOpenerPolicyConfig crossOriginOpenerPolicy = new CrossOriginOpenerPolicyConfig();
104+
105+
private final CrossOriginEmbedderPolicyConfig crossOriginEmbedderPolicy = new CrossOriginEmbedderPolicyConfig();
106+
107+
private final CrossOriginResourcePolicyConfig crossOriginResourcePolicy = new CrossOriginResourcePolicyConfig();
108+
100109
/**
101110
* Creates a new instance
102111
*
@@ -392,6 +401,9 @@ private List<HeaderWriter> getHeaderWriters() {
392401
addIfNotNull(writers, this.referrerPolicy.writer);
393402
addIfNotNull(writers, this.featurePolicy.writer);
394403
addIfNotNull(writers, this.permissionsPolicy.writer);
404+
addIfNotNull(writers, this.crossOriginOpenerPolicy.writer);
405+
addIfNotNull(writers, this.crossOriginEmbedderPolicy.writer);
406+
addIfNotNull(writers, this.crossOriginResourcePolicy.writer);
395407
writers.addAll(this.headerWriters);
396408
return writers;
397409
}
@@ -544,6 +556,129 @@ public PermissionsPolicyConfig permissionsPolicy(Customizer<PermissionsPolicyCon
544556
return this.permissionsPolicy;
545557
}
546558

559+
/**
560+
* Allows configuration for <a href=
561+
* "https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cross-Origin-Opener-Policy">
562+
* Cross-Origin-Opener-Policy</a> header.
563+
* <p>
564+
* Configuration is provided to the {@link CrossOriginOpenerPolicyHeaderWriter} which
565+
* responsible for writing the header.
566+
* </p>
567+
* @return the {@link CrossOriginOpenerPolicyConfig} for additional confniguration
568+
* @since 5.7
569+
* @see CrossOriginOpenerPolicyHeaderWriter
570+
*/
571+
public CrossOriginOpenerPolicyConfig crossOriginOpenerPolicy() {
572+
this.crossOriginOpenerPolicy.writer = new CrossOriginOpenerPolicyHeaderWriter();
573+
return this.crossOriginOpenerPolicy;
574+
}
575+
576+
/**
577+
* Allows configuration for <a href=
578+
* "https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cross-Origin-Opener-Policy">
579+
* Cross-Origin-Opener-Policy</a> header.
580+
* <p>
581+
* Calling this method automatically enables (includes) the
582+
* {@code Cross-Origin-Opener-Policy} header in the response using the supplied
583+
* policy.
584+
* <p>
585+
* <p>
586+
* Configuration is provided to the {@link CrossOriginOpenerPolicyHeaderWriter} which
587+
* responsible for writing the header.
588+
* </p>
589+
* @return the {@link HeadersConfigurer} for additional customizations
590+
* @since 5.7
591+
* @see CrossOriginOpenerPolicyHeaderWriter
592+
*/
593+
public HeadersConfigurer<H> crossOriginOpenerPolicy(
594+
Customizer<CrossOriginOpenerPolicyConfig> crossOriginOpenerPolicyCustomizer) {
595+
this.crossOriginOpenerPolicy.writer = new CrossOriginOpenerPolicyHeaderWriter();
596+
crossOriginOpenerPolicyCustomizer.customize(this.crossOriginOpenerPolicy);
597+
return HeadersConfigurer.this;
598+
}
599+
600+
/**
601+
* Allows configuration for <a href=
602+
* "https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cross-Origin-Embedder-Policy">
603+
* Cross-Origin-Embedder-Policy</a> header.
604+
* <p>
605+
* Configuration is provided to the {@link CrossOriginEmbedderPolicyHeaderWriter}
606+
* which is responsible for writing the header.
607+
* </p>
608+
* @return the {@link CrossOriginEmbedderPolicyConfig} for additional customizations
609+
* @since 5.7
610+
* @see CrossOriginEmbedderPolicyHeaderWriter
611+
*/
612+
public CrossOriginEmbedderPolicyConfig crossOriginEmbedderPolicy() {
613+
this.crossOriginEmbedderPolicy.writer = new CrossOriginEmbedderPolicyHeaderWriter();
614+
return this.crossOriginEmbedderPolicy;
615+
}
616+
617+
/**
618+
* Allows configuration for <a href=
619+
* "https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cross-Origin-Embedder-Policy">
620+
* Cross-Origin-Embedder-Policy</a> header.
621+
* <p>
622+
* Calling this method automatically enables (includes) the
623+
* {@code Cross-Origin-Embedder-Policy} header in the response using the supplied
624+
* policy.
625+
* <p>
626+
* <p>
627+
* Configuration is provided to the {@link CrossOriginEmbedderPolicyHeaderWriter}
628+
* which is responsible for writing the header.
629+
* </p>
630+
* @return the {@link HeadersConfigurer} for additional customizations
631+
* @since 5.7
632+
* @see CrossOriginEmbedderPolicyHeaderWriter
633+
*/
634+
public HeadersConfigurer<H> crossOriginEmbedderPolicy(
635+
Customizer<CrossOriginEmbedderPolicyConfig> crossOriginEmbedderPolicyCustomizer) {
636+
this.crossOriginEmbedderPolicy.writer = new CrossOriginEmbedderPolicyHeaderWriter();
637+
crossOriginEmbedderPolicyCustomizer.customize(this.crossOriginEmbedderPolicy);
638+
return HeadersConfigurer.this;
639+
}
640+
641+
/**
642+
* Allows configuration for <a href=
643+
* "https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cross-Origin-Resource-Policy">
644+
* Cross-Origin-Resource-Policy</a> header.
645+
* <p>
646+
* Configuration is provided to the {@link CrossOriginResourcePolicyHeaderWriter}
647+
* which is responsible for writing the header:
648+
* </p>
649+
* @return the {@link HeadersConfigurer} for additional customizations
650+
* @since 5.7
651+
* @see CrossOriginResourcePolicyHeaderWriter
652+
*/
653+
public CrossOriginResourcePolicyConfig crossOriginResourcePolicy() {
654+
this.crossOriginResourcePolicy.writer = new CrossOriginResourcePolicyHeaderWriter();
655+
return this.crossOriginResourcePolicy;
656+
}
657+
658+
/**
659+
* Allows configuration for <a href=
660+
* "https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cross-Origin-Resource-Policy">
661+
* Cross-Origin-Resource-Policy</a> header.
662+
* <p>
663+
* Calling this method automatically enables (includes) the
664+
* {@code Cross-Origin-Resource-Policy} header in the response using the supplied
665+
* policy.
666+
* <p>
667+
* <p>
668+
* Configuration is provided to the {@link CrossOriginResourcePolicyHeaderWriter}
669+
* which is responsible for writing the header:
670+
* </p>
671+
* @return the {@link HeadersConfigurer} for additional customizations
672+
* @since 5.7
673+
* @see CrossOriginResourcePolicyHeaderWriter
674+
*/
675+
public HeadersConfigurer<H> crossOriginResourcePolicy(
676+
Customizer<CrossOriginResourcePolicyConfig> crossOriginResourcePolicyCustomizer) {
677+
this.crossOriginResourcePolicy.writer = new CrossOriginResourcePolicyHeaderWriter();
678+
crossOriginResourcePolicyCustomizer.customize(this.crossOriginResourcePolicy);
679+
return HeadersConfigurer.this;
680+
}
681+
547682
public final class ContentTypeOptionsConfig {
548683

549684
private XContentTypeOptionsHeaderWriter writer;
@@ -1142,4 +1277,96 @@ public HeadersConfigurer<H> and() {
11421277

11431278
}
11441279

1280+
public final class CrossOriginOpenerPolicyConfig {
1281+
1282+
private CrossOriginOpenerPolicyHeaderWriter writer;
1283+
1284+
public CrossOriginOpenerPolicyConfig() {
1285+
}
1286+
1287+
/**
1288+
* Sets the policy to be used in the {@code Cross-Origin-Opener-Policy} header
1289+
* @param openerPolicy a {@code Cross-Origin-Opener-Policy}
1290+
* @return the {@link CrossOriginOpenerPolicyConfig} for additional configuration
1291+
* @throws IllegalArgumentException if openerPolicy is null
1292+
*/
1293+
public CrossOriginOpenerPolicyConfig policy(
1294+
CrossOriginOpenerPolicyHeaderWriter.CrossOriginOpenerPolicy openerPolicy) {
1295+
this.writer.setPolicy(openerPolicy);
1296+
return this;
1297+
}
1298+
1299+
/**
1300+
* Allows completing configuration of Cross Origin Opener Policy and continuing
1301+
* configuration of headers.
1302+
* @return the {@link HeadersConfigurer} for additional configuration
1303+
*/
1304+
public HeadersConfigurer<H> and() {
1305+
return HeadersConfigurer.this;
1306+
}
1307+
1308+
}
1309+
1310+
public final class CrossOriginEmbedderPolicyConfig {
1311+
1312+
private CrossOriginEmbedderPolicyHeaderWriter writer;
1313+
1314+
public CrossOriginEmbedderPolicyConfig() {
1315+
}
1316+
1317+
/**
1318+
* Sets the policy to be used in the {@code Cross-Origin-Embedder-Policy} header
1319+
* @param embedderPolicy a {@code Cross-Origin-Embedder-Policy}
1320+
* @return the {@link CrossOriginEmbedderPolicyConfig} for additional
1321+
* configuration
1322+
* @throws IllegalArgumentException if embedderPolicy is null
1323+
*/
1324+
public CrossOriginEmbedderPolicyConfig policy(
1325+
CrossOriginEmbedderPolicyHeaderWriter.CrossOriginEmbedderPolicy embedderPolicy) {
1326+
this.writer.setPolicy(embedderPolicy);
1327+
return this;
1328+
}
1329+
1330+
/**
1331+
* Allows completing configuration of Cross-Origin-Embedder-Policy and continuing
1332+
* configuration of headers.
1333+
* @return the {@link HeadersConfigurer} for additional configuration
1334+
*/
1335+
public HeadersConfigurer<H> and() {
1336+
return HeadersConfigurer.this;
1337+
}
1338+
1339+
}
1340+
1341+
public final class CrossOriginResourcePolicyConfig {
1342+
1343+
private CrossOriginResourcePolicyHeaderWriter writer;
1344+
1345+
public CrossOriginResourcePolicyConfig() {
1346+
}
1347+
1348+
/**
1349+
* Sets the policy to be used in the {@code Cross-Origin-Resource-Policy} header
1350+
* @param resourcePolicy a {@code Cross-Origin-Resource-Policy}
1351+
* @return the {@link CrossOriginResourcePolicyConfig} for additional
1352+
* configuration
1353+
* @throws IllegalArgumentException if resourcePolicy is null
1354+
*/
1355+
public CrossOriginResourcePolicyConfig policy(
1356+
CrossOriginResourcePolicyHeaderWriter.CrossOriginResourcePolicy resourcePolicy) {
1357+
this.writer.setPolicy(resourcePolicy);
1358+
return this;
1359+
}
1360+
1361+
/**
1362+
* Allows completing configuration of Cross-Origin-Resource-Policy and continuing
1363+
* configuration of headers.
1364+
* @return the {@link HeadersConfigurer} for additional configuration
1365+
*/
1366+
public HeadersConfigurer<H> and() {
1367+
return HeadersConfigurer.this;
1368+
}
1369+
1370+
}
1371+
11451372
}

config/src/main/java/org/springframework/security/config/http/HeadersBeanDefinitionParser.java

Lines changed: 82 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2019 the original author or authors.
2+
* Copyright 2002-2021 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -36,6 +36,9 @@
3636
import org.springframework.security.web.header.HeaderWriterFilter;
3737
import org.springframework.security.web.header.writers.CacheControlHeadersWriter;
3838
import org.springframework.security.web.header.writers.ContentSecurityPolicyHeaderWriter;
39+
import org.springframework.security.web.header.writers.CrossOriginEmbedderPolicyHeaderWriter;
40+
import org.springframework.security.web.header.writers.CrossOriginOpenerPolicyHeaderWriter;
41+
import org.springframework.security.web.header.writers.CrossOriginResourcePolicyHeaderWriter;
3942
import org.springframework.security.web.header.writers.FeaturePolicyHeaderWriter;
4043
import org.springframework.security.web.header.writers.HpkpHeaderWriter;
4144
import org.springframework.security.web.header.writers.HstsHeaderWriter;
@@ -122,6 +125,12 @@ public class HeadersBeanDefinitionParser implements BeanDefinitionParser {
122125

123126
private static final String PERMISSIONS_POLICY_ELEMENT = "permissions-policy";
124127

128+
private static final String CROSS_ORIGIN_OPENER_POLICY_ELEMENT = "cross-origin-opener-policy";
129+
130+
private static final String CROSS_ORIGIN_EMBEDDER_POLICY_ELEMENT = "cross-origin-embedder-policy";
131+
132+
private static final String CROSS_ORIGIN_RESOURCE_POLICY_ELEMENT = "cross-origin-resource-policy";
133+
125134
private static final String ALLOW_FROM = "ALLOW-FROM";
126135

127136
private ManagedList<BeanMetadataElement> headerWriters;
@@ -144,6 +153,9 @@ public BeanDefinition parse(Element element, ParserContext parserContext) {
144153
parseReferrerPolicyElement(element, parserContext);
145154
parseFeaturePolicyElement(element, parserContext);
146155
parsePermissionsPolicyElement(element, parserContext);
156+
parseCrossOriginOpenerPolicy(disabled, element);
157+
parseCrossOriginEmbedderPolicy(disabled, element);
158+
parseCrossOriginResourcePolicy(disabled, element);
147159
parseHeaderElements(element);
148160
boolean noWriters = this.headerWriters.isEmpty();
149161
if (disabled && !noWriters) {
@@ -376,6 +388,75 @@ private void addPermissionsPolicy(Element permissionsPolicyElement, ParserContex
376388
this.headerWriters.add(headersWriter.getBeanDefinition());
377389
}
378390

391+
private void parseCrossOriginOpenerPolicy(boolean elementDisabled, Element element) {
392+
if (elementDisabled || element == null) {
393+
return;
394+
}
395+
CrossOriginOpenerPolicyHeaderWriter writer = new CrossOriginOpenerPolicyHeaderWriter();
396+
Element crossOriginOpenerPolicyElement = DomUtils.getChildElementByTagName(element,
397+
CROSS_ORIGIN_OPENER_POLICY_ELEMENT);
398+
if (crossOriginOpenerPolicyElement != null) {
399+
addCrossOriginOpenerPolicy(crossOriginOpenerPolicyElement, writer);
400+
}
401+
BeanDefinitionBuilder builder = BeanDefinitionBuilder
402+
.genericBeanDefinition(CrossOriginOpenerPolicyHeaderWriter.class, () -> writer);
403+
this.headerWriters.add(builder.getBeanDefinition());
404+
}
405+
406+
private void parseCrossOriginEmbedderPolicy(boolean elementDisabled, Element element) {
407+
if (elementDisabled || element == null) {
408+
return;
409+
}
410+
CrossOriginEmbedderPolicyHeaderWriter writer = new CrossOriginEmbedderPolicyHeaderWriter();
411+
Element crossOriginEmbedderPolicyElement = DomUtils.getChildElementByTagName(element,
412+
CROSS_ORIGIN_EMBEDDER_POLICY_ELEMENT);
413+
if (crossOriginEmbedderPolicyElement != null) {
414+
addCrossOriginEmbedderPolicy(crossOriginEmbedderPolicyElement, writer);
415+
}
416+
BeanDefinitionBuilder builder = BeanDefinitionBuilder
417+
.genericBeanDefinition(CrossOriginEmbedderPolicyHeaderWriter.class, () -> writer);
418+
this.headerWriters.add(builder.getBeanDefinition());
419+
}
420+
421+
private void parseCrossOriginResourcePolicy(boolean elementDisabled, Element element) {
422+
if (elementDisabled || element == null) {
423+
return;
424+
}
425+
CrossOriginResourcePolicyHeaderWriter writer = new CrossOriginResourcePolicyHeaderWriter();
426+
Element crossOriginResourcePolicyElement = DomUtils.getChildElementByTagName(element,
427+
CROSS_ORIGIN_RESOURCE_POLICY_ELEMENT);
428+
if (crossOriginResourcePolicyElement != null) {
429+
addCrossOriginResourcePolicy(crossOriginResourcePolicyElement, writer);
430+
}
431+
BeanDefinitionBuilder builder = BeanDefinitionBuilder
432+
.genericBeanDefinition(CrossOriginResourcePolicyHeaderWriter.class, () -> writer);
433+
this.headerWriters.add(builder.getBeanDefinition());
434+
}
435+
436+
private void addCrossOriginResourcePolicy(Element crossOriginResourcePolicyElement,
437+
CrossOriginResourcePolicyHeaderWriter writer) {
438+
String policy = crossOriginResourcePolicyElement.getAttribute(ATT_POLICY);
439+
if (StringUtils.hasText(policy)) {
440+
writer.setPolicy(CrossOriginResourcePolicyHeaderWriter.CrossOriginResourcePolicy.from(policy));
441+
}
442+
}
443+
444+
private void addCrossOriginEmbedderPolicy(Element crossOriginEmbedderPolicyElement,
445+
CrossOriginEmbedderPolicyHeaderWriter writer) {
446+
String policy = crossOriginEmbedderPolicyElement.getAttribute(ATT_POLICY);
447+
if (StringUtils.hasText(policy)) {
448+
writer.setPolicy(CrossOriginEmbedderPolicyHeaderWriter.CrossOriginEmbedderPolicy.from(policy));
449+
}
450+
}
451+
452+
private void addCrossOriginOpenerPolicy(Element crossOriginOpenerPolicyElement,
453+
CrossOriginOpenerPolicyHeaderWriter writer) {
454+
String policy = crossOriginOpenerPolicyElement.getAttribute(ATT_POLICY);
455+
if (StringUtils.hasText(policy)) {
456+
writer.setPolicy(CrossOriginOpenerPolicyHeaderWriter.CrossOriginOpenerPolicy.from(policy));
457+
}
458+
}
459+
379460
private void attrNotAllowed(ParserContext context, String attrName, String otherAttrName, Element element) {
380461
context.getReaderContext().error("Only one of '" + attrName + "' or '" + otherAttrName + "' can be set.",
381462
element);

0 commit comments

Comments
 (0)