diff --git a/spring-web/src/main/java/org/springframework/web/bind/annotation/CrossOrigin.java b/spring-web/src/main/java/org/springframework/web/bind/annotation/CrossOrigin.java
index 8e2de23b5d4..ca755049783 100644
--- a/spring-web/src/main/java/org/springframework/web/bind/annotation/CrossOrigin.java
+++ b/spring-web/src/main/java/org/springframework/web/bind/annotation/CrossOrigin.java
@@ -21,6 +21,7 @@ import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
+import java.util.List;
import org.springframework.core.annotation.AliasFor;
import org.springframework.web.cors.CorsConfiguration;
@@ -77,37 +78,20 @@ public @interface CrossOrigin {
String[] value() default {};
/**
- * The list of allowed origins that be specific origins, e.g.
- * {@code "https://domain1.com"}, or {@code "*"} for all origins.
- *
A matched origin is listed in the {@code Access-Control-Allow-Origin}
- * response header of preflight actual CORS requests.
- *
By default all origins are allowed.
- *
Note: CORS checks use values from "Forwarded"
- * (RFC 7239),
- * "X-Forwarded-Host", "X-Forwarded-Port", and "X-Forwarded-Proto" headers,
- * if present, in order to reflect the client-originated address.
- * Consider using the {@code ForwardedHeaderFilter} in order to choose from a
- * central place whether to extract and use, or to discard such headers.
- * See the Spring Framework reference for more on this filter.
- * @see #value
+ * A list of origins for which cross-origin requests are allowed. Please,
+ * see {@link CorsConfiguration#setAllowedOrigins(List)} for details.
+ *
By default all origins are allowed unless {@code originPatterns} is
+ * also set in which case {@code originPatterns} is used instead.
*/
@AliasFor("value")
String[] origins() default {};
/**
- * The list of allowed origins patterns that be specific origins, e.g.
- * {@code ".*\.domain1\.com"}, or {@code ".*"} for matching all origins.
- *
A matched origin is listed in the {@code Access-Control-Allow-Origin}
- * response header of preflight actual CORS requests.
- *
By default all origins are allowed.
- *
Note: CORS checks use values from "Forwarded"
- * (RFC 7239),
- * "X-Forwarded-Host", "X-Forwarded-Port", and "X-Forwarded-Proto" headers,
- * if present, in order to reflect the client-originated address.
- * Consider using the {@code ForwardedHeaderFilter} in order to choose from a
- * central place whether to extract and use, or to discard such headers.
- * See the Spring Framework reference for more on this filter.
- * @see #value
+ * Alternative to {@link #origins()} that supports origins declared via
+ * wildcard patterns. Please, see
+ * @link CorsConfiguration#setAllowedOriginPatterns(List)} for details.
+ *
By default this is not set.
+ * @since 5.3
*/
String[] originPatterns() default {};
diff --git a/spring-web/src/main/java/org/springframework/web/cors/CorsConfiguration.java b/spring-web/src/main/java/org/springframework/web/cors/CorsConfiguration.java
index db63d61fa5a..6ca27ac9bab 100644
--- a/spring-web/src/main/java/org/springframework/web/cors/CorsConfiguration.java
+++ b/spring-web/src/main/java/org/springframework/web/cors/CorsConfiguration.java
@@ -54,30 +54,30 @@ public class CorsConfiguration {
/** Wildcard representing all origins, methods, or headers. */
public static final String ALL = "*";
- /** Wildcard representing pattern that matches all origins. */
- public static final String ALL_PATTERN = ".*";
- private static final List DEFAULT_METHODS = Collections.unmodifiableList(
- Arrays.asList(HttpMethod.GET, HttpMethod.HEAD));
+ private static final List ALL_LIST = Collections.unmodifiableList(
+ Collections.singletonList(ALL));
- private static final List DEFAULT_PERMIT_METHODS = Collections.unmodifiableList(
- Arrays.asList(HttpMethod.GET.name(), HttpMethod.HEAD.name(), HttpMethod.POST.name()));
+ private static final OriginPattern ALL_PATTERN = new OriginPattern("*");
+
+ private static final List ALL_PATTERN_LIST = Collections.unmodifiableList(
+ Collections.singletonList(ALL_PATTERN));
private static final List DEFAULT_PERMIT_ALL = Collections.unmodifiableList(
Collections.singletonList(ALL));
- private static final List DEFAULT_PERMIT_ALL_PATTERN_STR = Collections.unmodifiableList(
- Collections.singletonList(ALL_PATTERN));
+ private static final List DEFAULT_METHODS = Collections.unmodifiableList(
+ Arrays.asList(HttpMethod.GET, HttpMethod.HEAD));
- private static final List DEFAULT_PERMIT_ALL_PATTERN = Collections.unmodifiableList(
- Collections.singletonList(Pattern.compile(ALL_PATTERN)));
+ private static final List DEFAULT_PERMIT_METHODS = Collections.unmodifiableList(
+ Arrays.asList(HttpMethod.GET.name(), HttpMethod.HEAD.name(), HttpMethod.POST.name()));
@Nullable
private List allowedOrigins;
@Nullable
- private List allowedOriginPatterns;
+ private List allowedOriginPatterns;
@Nullable
private List allowedMethods;
@@ -123,9 +123,19 @@ public class CorsConfiguration {
/**
- * Set the origins to allow, e.g. {@code "https://domain1.com"}.
- * The special value {@code "*"} allows all domains.
- *
By default this is not set.
+ * A list of origins for which cross-origin requests are allowed. Values may
+ * be a specific domain, e.g. {@code "https://domain1.com"}, or the CORS
+ * defined special value {@code "*"} for all origins.
+ *
For matched pre-flight and actual requests the
+ * {@code Access-Control-Allow-Origin} response header is set either to the
+ * matched domain value or to {@code "*"}. Keep in mind however that the
+ * CORS spec does not allow {@code "*"} when {@link #setAllowCredentials
+ * allowCredentials} is set to {@code true} and as of 5.3 that combination
+ * is rejected in favor of using {@link #setAllowedOriginPatterns
+ * allowedOriginPatterns} instead.
+ *
By default this is not set which means that no origins are allowed.
+ * However an instance of this class is often initialized further, e.g. for
+ * {@code @CrossOrigin}, via {@link #applyPermitDefaultValues()}.
*/
public void setAllowedOrigins(@Nullable List allowedOrigins) {
this.allowedOrigins = (allowedOrigins != null ? new ArrayList<>(allowedOrigins) : null);
@@ -133,8 +143,6 @@ public class CorsConfiguration {
/**
* Return the configured origins to allow, or {@code null} if none.
- * @see #addAllowedOrigin(String)
- * @see #setAllowedOrigins(List)
*/
@Nullable
public List getAllowedOrigins() {
@@ -142,21 +150,29 @@ public class CorsConfiguration {
}
/**
- * Add an origin to allow.
+ * Variant of {@link #setAllowedOrigins} for adding one origin at a time.
*/
public void addAllowedOrigin(String origin) {
if (this.allowedOrigins == null) {
this.allowedOrigins = new ArrayList<>(4);
}
- else if (this.allowedOrigins == DEFAULT_PERMIT_ALL) {
+ else if (this.allowedOrigins == DEFAULT_PERMIT_ALL && CollectionUtils.isEmpty(this.allowedOriginPatterns)) {
setAllowedOrigins(DEFAULT_PERMIT_ALL);
}
this.allowedOrigins.add(origin);
}
/**
- * Set the origins patterns to allow, e.g. {@code "*.com"}.
+ * Alternative to {@link #setAllowedOrigins} that supports origins declared
+ * via wildcard patterns. In contrast to {@link #setAllowedOrigins allowedOrigins}
+ * which does support the special value {@code "*"}, this property allows
+ * more flexible patterns, e.g. {@code "https://*.domain1.com"}. Furthermore
+ * it always sets the {@code Access-Control-Allow-Origin} response header to
+ * the matched origin and never to {@code "*"}, nor to any other pattern, and
+ * therefore can be used in combination with {@link #setAllowCredentials}
+ * set to {@code true}.
* By default this is not set.
+ * @since 5.3
*/
public CorsConfiguration setAllowedOriginPatterns(@Nullable List allowedOriginPatterns) {
if (allowedOriginPatterns == null) {
@@ -164,42 +180,39 @@ public class CorsConfiguration {
}
else {
this.allowedOriginPatterns = new ArrayList<>(allowedOriginPatterns.size());
- for (String pattern : allowedOriginPatterns) {
- this.allowedOriginPatterns.add(Pattern.compile(pattern));
+ for (String patternValue : allowedOriginPatterns) {
+ addAllowedOriginPattern(patternValue);
}
}
-
return this;
}
/**
* Return the configured origins patterns to allow, or {@code null} if none.
- *
- * @see #addAllowedOriginPattern(String)
- * @see #setAllowedOriginPatterns(List)
+ * @since 5.3
*/
@Nullable
public List getAllowedOriginPatterns() {
if (this.allowedOriginPatterns == null) {
return null;
}
- if (this.allowedOriginPatterns == DEFAULT_PERMIT_ALL_PATTERN) {
- return DEFAULT_PERMIT_ALL_PATTERN_STR;
- }
- return this.allowedOriginPatterns.stream().map(Pattern::toString).collect(Collectors.toList());
+ return this.allowedOriginPatterns.stream()
+ .map(OriginPattern::getDeclaredPattern)
+ .collect(Collectors.toList());
}
/**
- * Add an origin pattern to allow.
+ * Variant of {@link #setAllowedOriginPatterns} for adding one origin at a time.
+ * @since 5.3
*/
public void addAllowedOriginPattern(String originPattern) {
if (this.allowedOriginPatterns == null) {
this.allowedOriginPatterns = new ArrayList<>(4);
}
- else if (this.allowedOriginPatterns == DEFAULT_PERMIT_ALL_PATTERN) {
- setAllowedOriginPatterns(DEFAULT_PERMIT_ALL_PATTERN_STR);
+ this.allowedOriginPatterns.add(new OriginPattern(originPattern));
+ if (this.allowedOrigins == DEFAULT_PERMIT_ALL) {
+ this.allowedOrigins = null;
}
- this.allowedOriginPatterns.add(Pattern.compile(originPattern));
}
/**
@@ -397,16 +410,15 @@ public class CorsConfiguration {
/**
- * By default a newly created {@code CorsConfiguration} does not permit any
- * cross-origin requests and must be configured explicitly to indicate what
- * should be allowed.
- * Use this method to flip the initialization model to start with open
- * defaults that permit all cross-origin requests for GET, HEAD, and POST
- * requests. Note however that this method will not override any existing
- * values already set.
- *
The following defaults are applied if not already set:
+ * By default {@code CorsConfiguration} does not permit any cross-origin
+ * requests and must be configured explicitly. Use this method to switch to
+ * defaults that permit all cross-origin requests for GET, HEAD, and POST,
+ * but not overriding any values that have already been set.
+ *
The following defaults are applied for values that are not set:
*
- * - Allow all origins.
+ * - Allow all origins with the special value {@code "*"} defined in the
+ * CORS spec. This is set only if neither {@link #setAllowedOrigins origins}
+ * nor {@link #setAllowedOriginPatterns originPatterns} are already set.
* - Allow "simple" methods {@code GET}, {@code HEAD} and {@code POST}.
* - Allow all headers.
* - Set max age to 1800 seconds (30 minutes).
@@ -430,6 +442,26 @@ public class CorsConfiguration {
return this;
}
+ /**
+ * Validate that when {@link #setAllowCredentials allowCredentials} is true,
+ * {@link #setAllowedOrigins allowedOrigins} does not contain the special
+ * value {@code "*"} since in that case the "Access-Control-Allow-Origin"
+ * cannot be set to {@code "*"}.
+ * @throws IllegalArgumentException if the validation fails
+ * @since 5.3
+ */
+ public void validateAllowCredentials() {
+ if (this.allowCredentials == Boolean.TRUE &&
+ this.allowedOrigins != null && this.allowedOrigins.contains(ALL)) {
+
+ throw new IllegalArgumentException(
+ "When allowCredentials is true, allowedOrigins cannot contain the special value \"*\"" +
+ "since that cannot be set on the \"Access-Control-Allow-Origin\" response header. " +
+ "To allow credentials to a set of origins, list them explicitly " +
+ "or consider using \"allowedOriginPatterns\" instead.");
+ }
+ }
+
/**
* Combine the non-null properties of the supplied
* {@code CorsConfiguration} with this one.
@@ -439,12 +471,11 @@ public class CorsConfiguration {
* Combining lists like {@code allowedOrigins}, {@code allowedMethods},
* {@code allowedHeaders} or {@code exposedHeaders} is done in an additive
* way. For example, combining {@code ["GET", "POST"]} with
- * {@code ["PATCH"]} results in {@code ["GET", "POST", "PATCH"]}, but keep
- * in mind that combining {@code ["GET", "POST"]} with {@code ["*"]}
- * results in {@code ["*"]}.
- *
Notice that default permit values set by
+ * {@code ["PATCH"]} results in {@code ["GET", "POST", "PATCH"]}. However,
+ * combining {@code ["GET", "POST"]} with {@code ["*"]} results in
+ * {@code ["*"]}. Note also that default permit values set by
* {@link CorsConfiguration#applyPermitDefaultValues()} are overridden by
- * any value explicitly defined.
+ * any explicitly defined values.
* @return the combined {@code CorsConfiguration}, or {@code this}
* configuration if the supplied configuration is {@code null}
*/
@@ -453,15 +484,12 @@ public class CorsConfiguration {
if (other == null) {
return this;
}
+ // Bypass setAllowedOrigins to avoid re-compiling patterns
CorsConfiguration config = new CorsConfiguration(this);
- List combinedOrigins = combine(getAllowedOrigins(), other.getAllowedOrigins());
- List combinedOriginPatterns = combine(getAllowedOriginPatterns(), other.getAllowedOriginPatterns());
- if (combinedOrigins == DEFAULT_PERMIT_ALL && combinedOriginPatterns != DEFAULT_PERMIT_ALL_PATTERN_STR
- && !CollectionUtils.isEmpty(combinedOriginPatterns)) {
- combinedOrigins = null;
- }
- config.setAllowedOrigins(combinedOrigins);
- config.setAllowedOriginPatterns(combinedOriginPatterns);
+ List origins = combine(getAllowedOrigins(), other.getAllowedOrigins());
+ List patterns = combinePatterns(this.allowedOriginPatterns, other.allowedOriginPatterns);
+ config.allowedOrigins = (origins == DEFAULT_PERMIT_ALL && !CollectionUtils.isEmpty(patterns) ? null : origins);
+ config.allowedOriginPatterns = patterns;
config.setAllowedMethods(combine(getAllowedMethods(), other.getAllowedMethods()));
config.setAllowedHeaders(combine(getAllowedHeaders(), other.getAllowedHeaders()));
config.setExposedHeaders(combine(getExposedHeaders(), other.getExposedHeaders()));
@@ -483,25 +511,40 @@ public class CorsConfiguration {
if (source == null) {
return other;
}
- if (source == DEFAULT_PERMIT_ALL || source == DEFAULT_PERMIT_METHODS
- || source == DEFAULT_PERMIT_ALL_PATTERN_STR) {
+ if (source == DEFAULT_PERMIT_ALL || source == DEFAULT_PERMIT_METHODS) {
return other;
}
- if (other == DEFAULT_PERMIT_ALL || other == DEFAULT_PERMIT_METHODS
- || other == DEFAULT_PERMIT_ALL_PATTERN_STR) {
+ if (other == DEFAULT_PERMIT_ALL || other == DEFAULT_PERMIT_METHODS) {
return source;
}
if (source.contains(ALL) || other.contains(ALL)) {
- return new ArrayList<>(Collections.singletonList(ALL));
+ return ALL_LIST;
+ }
+ Set combined = new LinkedHashSet<>(source.size() + other.size());
+ combined.addAll(source);
+ combined.addAll(other);
+ return new ArrayList<>(combined);
+ }
+
+ private List combinePatterns(
+ @Nullable List source, @Nullable List other) {
+
+ if (other == null) {
+ return (source != null ? source : Collections.emptyList());
}
- if ( source.contains(ALL_PATTERN) || other.contains(ALL_PATTERN)) {
- return new ArrayList<>(Collections.singletonList(ALL_PATTERN));
+ if (source == null) {
+ return other;
}
- Set combined = new LinkedHashSet<>(source);
+ if (source.contains(ALL_PATTERN) || other.contains(ALL_PATTERN)) {
+ return ALL_PATTERN_LIST;
+ }
+ Set combined = new LinkedHashSet<>(source.size() + other.size());
+ combined.addAll(source);
combined.addAll(other);
return new ArrayList<>(combined);
}
+
/**
* Check the origin of the request against the configured allowed origins.
* @param requestOrigin the origin to check
@@ -513,15 +556,10 @@ public class CorsConfiguration {
if (!StringUtils.hasText(requestOrigin)) {
return null;
}
-
if (!ObjectUtils.isEmpty(this.allowedOrigins)) {
if (this.allowedOrigins.contains(ALL)) {
- if (this.allowCredentials != Boolean.TRUE) {
- return ALL;
- }
- else {
- return requestOrigin;
- }
+ validateAllowCredentials();
+ return ALL;
}
for (String allowedOrigin : this.allowedOrigins) {
if (requestOrigin.equalsIgnoreCase(allowedOrigin)) {
@@ -530,21 +568,12 @@ public class CorsConfiguration {
}
}
if (!ObjectUtils.isEmpty(this.allowedOriginPatterns)) {
- for (Pattern allowedOriginsPattern : this.allowedOriginPatterns) {
- if (allowedOriginsPattern.pattern().equals(ALL_PATTERN)) {
- if (this.allowCredentials != Boolean.TRUE) {
- return ALL;
- }
- else {
- return requestOrigin;
- }
- }
- else if (allowedOriginsPattern.matcher(requestOrigin).matches()) {
+ for (OriginPattern p : this.allowedOriginPatterns) {
+ if (p.getDeclaredPattern().equals(ALL) || p.getPattern().matcher(requestOrigin).matches()) {
return requestOrigin;
}
}
}
-
return null;
}
@@ -608,4 +637,57 @@ public class CorsConfiguration {
return (result.isEmpty() ? null : result);
}
+
+ /**
+ * Contains both the user-declared pattern (e.g. "https://*.domain.com") and
+ * the regex {@link Pattern} derived from it.
+ */
+ private static class OriginPattern {
+
+ private final String declaredPattern;
+
+ private final Pattern pattern;
+
+ OriginPattern(String declaredPattern) {
+ this.declaredPattern = declaredPattern;
+ this.pattern = toPattern(declaredPattern);
+ }
+
+ private static Pattern toPattern(String patternValue) {
+ patternValue = "\\Q" + patternValue + "\\E";
+ patternValue = patternValue.replace("*", "\\E.*\\Q");
+ return Pattern.compile(patternValue);
+ }
+
+ public String getDeclaredPattern() {
+ return this.declaredPattern;
+ }
+
+ public Pattern getPattern() {
+ return this.pattern;
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (this == other) {
+ return true;
+ }
+ if (other == null || !getClass().equals(other.getClass())) {
+ return false;
+ }
+ return ObjectUtils.nullSafeEquals(
+ this.declaredPattern, ((OriginPattern) other).declaredPattern);
+ }
+
+ @Override
+ public int hashCode() {
+ return this.declaredPattern.hashCode();
+ }
+
+ @Override
+ public String toString() {
+ return this.declaredPattern;
+ }
+ }
+
}
diff --git a/spring-web/src/test/java/org/springframework/web/cors/CorsConfigurationTests.java b/spring-web/src/test/java/org/springframework/web/cors/CorsConfigurationTests.java
index fbff8c6d8aa..446fd81c013 100644
--- a/spring-web/src/test/java/org/springframework/web/cors/CorsConfigurationTests.java
+++ b/spring-web/src/test/java/org/springframework/web/cors/CorsConfigurationTests.java
@@ -40,6 +40,8 @@ public class CorsConfigurationTests {
CorsConfiguration config = new CorsConfiguration();
config.setAllowedOrigins(null);
assertThat(config.getAllowedOrigins()).isNull();
+ config.setAllowedOriginPatterns(null);
+ assertThat(config.getAllowedOriginPatterns()).isNull();
config.setAllowedHeaders(null);
assertThat(config.getAllowedHeaders()).isNull();
config.setAllowedMethods(null);
@@ -50,42 +52,39 @@ public class CorsConfigurationTests {
assertThat(config.getAllowCredentials()).isNull();
config.setMaxAge((Long) null);
assertThat(config.getMaxAge()).isNull();
- config.setAllowedOriginPatterns(null);
- assertThat(config.getAllowedOriginPatterns()).isNull();
}
@Test
public void setValues() {
CorsConfiguration config = new CorsConfiguration();
config.addAllowedOrigin("*");
- assertThat(config.getAllowedOrigins()).containsExactly("*");
+ config.addAllowedOriginPattern("http://*.example.com");
config.addAllowedHeader("*");
- assertThat(config.getAllowedHeaders()).containsExactly("*");
config.addAllowedMethod("*");
- assertThat(config.getAllowedMethods()).containsExactly("*");
config.addExposedHeader("header1");
config.addExposedHeader("header2");
- assertThat(config.getExposedHeaders()).containsExactly("header1", "header2");
config.setAllowCredentials(true);
- assertThat(config.getAllowCredentials()).isTrue();
config.setMaxAge(123L);
+
+ assertThat(config.getAllowedOrigins()).containsExactly("*");
+ assertThat(config.getAllowedOriginPatterns()).containsExactly("http://*.example.com");
+ assertThat(config.getAllowedHeaders()).containsExactly("*");
+ assertThat(config.getAllowedMethods()).containsExactly("*");
+ assertThat(config.getExposedHeaders()).containsExactly("header1", "header2");
+ assertThat(config.getAllowCredentials()).isTrue();
assertThat(config.getMaxAge()).isEqualTo(new Long(123));
- config.addAllowedOriginPattern(".*\\.example\\.com");
- assertThat(config.getAllowedOriginPatterns()).containsExactly(".*\\.example\\.com");
}
@Test
public void asteriskWildCardOnAddExposedHeader() {
- CorsConfiguration config = new CorsConfiguration();
- assertThatIllegalArgumentException().isThrownBy(() ->
- config.addExposedHeader("*"));
+ assertThatIllegalArgumentException()
+ .isThrownBy(() -> new CorsConfiguration().addExposedHeader("*"));
}
@Test
public void asteriskWildCardOnSetExposedHeaders() {
- CorsConfiguration config = new CorsConfiguration();
assertThatIllegalArgumentException()
- .isThrownBy(() -> config.setExposedHeaders(Collections.singletonList("*")));
+ .isThrownBy(() -> new CorsConfiguration().setExposedHeaders(Collections.singletonList("*")));
}
@Test
@@ -94,28 +93,31 @@ public class CorsConfigurationTests {
config.setAllowedOrigins(Collections.singletonList("*"));
config.combine(null);
assertThat(config.getAllowedOrigins()).containsExactly("*");
+ assertThat(config.getAllowedOriginPatterns()).isNull();
}
@Test
public void combineWithNullProperties() {
CorsConfiguration config = new CorsConfiguration();
config.addAllowedOrigin("*");
+ config.setAllowedOriginPatterns(Collections.singletonList("http://*.example.com"));
config.addAllowedHeader("header1");
config.addExposedHeader("header3");
config.addAllowedMethod(HttpMethod.GET.name());
config.setMaxAge(123L);
config.setAllowCredentials(true);
- config.setAllowedOriginPatterns(Collections.singletonList(".*\\.example\\.com"));
+
CorsConfiguration other = new CorsConfiguration();
config = config.combine(other);
+
assertThat(config).isNotNull();
assertThat(config.getAllowedOrigins()).containsExactly("*");
+ assertThat(config.getAllowedOriginPatterns()).containsExactly("http://*.example.com");
assertThat(config.getAllowedHeaders()).containsExactly("header1");
assertThat(config.getExposedHeaders()).containsExactly("header3");
assertThat(config.getAllowedMethods()).containsExactly(HttpMethod.GET.name());
assertThat(config.getMaxAge()).isEqualTo(new Long(123));
assertThat(config.getAllowCredentials()).isTrue();
- assertThat(config.getAllowedOriginPatterns()).containsExactly(".*\\.example\\.com");
}
@Test // SPR-15772
@@ -157,35 +159,36 @@ public class CorsConfigurationTests {
public void combinePatternWithDefaultPermitValues() {
CorsConfiguration config = new CorsConfiguration().applyPermitDefaultValues();
CorsConfiguration other = new CorsConfiguration();
- other.addAllowedOriginPattern(".*\\.com");
+ other.addAllowedOriginPattern("http://*.com");
CorsConfiguration combinedConfig = other.combine(config);
assertThat(combinedConfig).isNotNull();
assertThat(combinedConfig.getAllowedOrigins()).isNull();
- assertThat(combinedConfig.getAllowedOriginPatterns()).containsExactly(".*\\.com");
+ assertThat(combinedConfig.getAllowedOriginPatterns()).containsExactly("http://*.com");
combinedConfig = config.combine(other);
assertThat(combinedConfig).isNotNull();
assertThat(combinedConfig.getAllowedOrigins()).isNull();
- assertThat(combinedConfig.getAllowedOriginPatterns()).containsExactly(".*\\.com");
+ assertThat(combinedConfig.getAllowedOriginPatterns()).containsExactly("http://*.com");
}
@Test
public void combinePatternWithDefaultPermitValuesAndCustomOrigin() {
CorsConfiguration config = new CorsConfiguration().applyPermitDefaultValues();
config.setAllowedOrigins(Collections.singletonList("https://domain.com"));
+
CorsConfiguration other = new CorsConfiguration();
- other.addAllowedOriginPattern(".*\\.com");
+ other.addAllowedOriginPattern("http://*.com");
CorsConfiguration combinedConfig = other.combine(config);
assertThat(combinedConfig).isNotNull();
assertThat(combinedConfig.getAllowedOrigins()).containsExactly("https://domain.com");
- assertThat(combinedConfig.getAllowedOriginPatterns()).containsExactly(".*\\.com");
+ assertThat(combinedConfig.getAllowedOriginPatterns()).containsExactly("http://*.com");
combinedConfig = config.combine(other);
assertThat(combinedConfig).isNotNull();
assertThat(combinedConfig.getAllowedOrigins()).containsExactly("https://domain.com");
- assertThat(combinedConfig.getAllowedOriginPatterns()).containsExactly(".*\\.com");
+ assertThat(combinedConfig.getAllowedOriginPatterns()).containsExactly("http://*.com");
}
@Test
@@ -194,25 +197,28 @@ public class CorsConfigurationTests {
config.addAllowedOrigin("*");
config.addAllowedHeader("*");
config.addAllowedMethod("*");
- config.addAllowedOriginPattern(".*");
+ config.addAllowedOriginPattern("*");
+
CorsConfiguration other = new CorsConfiguration();
other.addAllowedOrigin("https://domain.com");
+ other.addAllowedOriginPattern("http://*.company.com");
other.addAllowedHeader("header1");
other.addExposedHeader("header2");
- other.addAllowedOriginPattern(".*\\.company\\.com");
other.addAllowedMethod(HttpMethod.PUT.name());
+
CorsConfiguration combinedConfig = config.combine(other);
assertThat(combinedConfig).isNotNull();
assertThat(combinedConfig.getAllowedOrigins()).containsExactly("*");
+ assertThat(combinedConfig.getAllowedOriginPatterns()).containsExactly("*");
assertThat(combinedConfig.getAllowedHeaders()).containsExactly("*");
assertThat(combinedConfig.getAllowedMethods()).containsExactly("*");
- assertThat(combinedConfig.getAllowedOriginPatterns()).containsExactly(".*");
+
combinedConfig = other.combine(config);
assertThat(combinedConfig).isNotNull();
assertThat(combinedConfig.getAllowedOrigins()).containsExactly("*");
+ assertThat(combinedConfig.getAllowedOriginPatterns()).containsExactly("*");
assertThat(combinedConfig.getAllowedHeaders()).containsExactly("*");
assertThat(combinedConfig.getAllowedMethods()).containsExactly("*");
- assertThat(combinedConfig.getAllowedOriginPatterns()).containsExactly(".*");
}
@Test // SPR-14792
@@ -226,41 +232,45 @@ public class CorsConfigurationTests {
config.addExposedHeader("header4");
config.addAllowedMethod(HttpMethod.GET.name());
config.addAllowedMethod(HttpMethod.PUT.name());
- config.addAllowedOriginPattern(".*\\.domain1\\.com");
- config.addAllowedOriginPattern(".*\\.domain2\\.com");
+ config.addAllowedOriginPattern("http://*.domain1.com");
+ config.addAllowedOriginPattern("http://*.domain2.com");
+
CorsConfiguration other = new CorsConfiguration();
other.addAllowedOrigin("https://domain1.com");
+ other.addAllowedOriginPattern("http://*.domain1.com");
other.addAllowedHeader("header1");
other.addExposedHeader("header3");
other.addAllowedMethod(HttpMethod.GET.name());
- other.addAllowedOriginPattern(".*\\.domain1\\.com");
+
CorsConfiguration combinedConfig = config.combine(other);
assertThat(combinedConfig).isNotNull();
assertThat(combinedConfig.getAllowedOrigins()).containsExactly("https://domain1.com", "https://domain2.com");
assertThat(combinedConfig.getAllowedHeaders()).containsExactly("header1", "header2");
assertThat(combinedConfig.getExposedHeaders()).containsExactly("header3", "header4");
assertThat(combinedConfig.getAllowedMethods()).containsExactly(HttpMethod.GET.name(), HttpMethod.PUT.name());
- assertThat(combinedConfig.getAllowedOriginPatterns()).containsExactly(".*\\.domain1\\.com", ".*\\.domain2\\.com");
+ assertThat(combinedConfig.getAllowedOriginPatterns()).containsExactly("http://*.domain1.com", "http://*.domain2.com");
}
@Test
public void combine() {
CorsConfiguration config = new CorsConfiguration();
config.addAllowedOrigin("https://domain1.com");
+ config.addAllowedOriginPattern("http://*.domain1.com");
config.addAllowedHeader("header1");
config.addExposedHeader("header3");
config.addAllowedMethod(HttpMethod.GET.name());
config.setMaxAge(123L);
config.setAllowCredentials(true);
- config.addAllowedOriginPattern(".*\\.domain1\\.com");
+
CorsConfiguration other = new CorsConfiguration();
other.addAllowedOrigin("https://domain2.com");
+ other.addAllowedOriginPattern("http://*.domain2.com");
other.addAllowedHeader("header2");
other.addExposedHeader("header4");
other.addAllowedMethod(HttpMethod.PUT.name());
other.setMaxAge(456L);
other.setAllowCredentials(false);
- other.addAllowedOriginPattern(".*\\.domain2\\.com");
+
config = config.combine(other);
assertThat(config).isNotNull();
assertThat(config.getAllowedOrigins()).containsExactly("https://domain1.com", "https://domain2.com");
@@ -270,18 +280,21 @@ public class CorsConfigurationTests {
assertThat(config.getMaxAge()).isEqualTo(new Long(456));
assertThat(config).isNotNull();
assertThat(config.getAllowCredentials()).isFalse();
- assertThat(config.getAllowedOriginPatterns()).containsExactly(".*\\.domain1\\.com", ".*\\.domain2\\.com");
+ assertThat(config.getAllowedOriginPatterns()).containsExactly("http://*.domain1.com", "http://*.domain2.com");
}
@Test
public void checkOriginAllowed() {
CorsConfiguration config = new CorsConfiguration();
- config.setAllowedOrigins(Collections.singletonList("*"));
+ config.addAllowedOrigin("*");
assertThat(config.checkOrigin("https://domain.com")).isEqualTo("*");
+
config.setAllowCredentials(true);
- assertThat(config.checkOrigin("https://domain.com")).isEqualTo("https://domain.com");
+ assertThatIllegalArgumentException().isThrownBy(() -> config.checkOrigin("https://domain.com"));
+
config.setAllowedOrigins(Collections.singletonList("https://domain.com"));
assertThat(config.checkOrigin("https://domain.com")).isEqualTo("https://domain.com");
+
config.setAllowCredentials(false);
assertThat(config.checkOrigin("https://domain.com")).isEqualTo("https://domain.com");
}
@@ -291,10 +304,13 @@ public class CorsConfigurationTests {
CorsConfiguration config = new CorsConfiguration();
assertThat(config.checkOrigin(null)).isNull();
assertThat(config.checkOrigin("https://domain.com")).isNull();
+
config.addAllowedOrigin("*");
assertThat(config.checkOrigin(null)).isNull();
+
config.setAllowedOrigins(Collections.singletonList("https://domain1.com"));
assertThat(config.checkOrigin("https://domain2.com")).isNull();
+
config.setAllowedOrigins(new ArrayList<>());
assertThat(config.checkOrigin("https://domain.com")).isNull();
}
@@ -302,12 +318,17 @@ public class CorsConfigurationTests {
@Test
public void checkOriginPatternAllowed() {
CorsConfiguration config = new CorsConfiguration();
- config.setAllowedOriginPatterns(Collections.singletonList(".*"));
+ assertThat(config.checkOrigin("https://domain.com")).isNull();
+
+ config.applyPermitDefaultValues();
assertThat(config.checkOrigin("https://domain.com")).isEqualTo("*");
+
config.setAllowCredentials(true);
- assertThat(config.checkOrigin("https://domain.com")).isEqualTo("https://domain.com");
- config.setAllowedOriginPatterns(Collections.singletonList(".*\\.domain\\.com"));
+ assertThatIllegalArgumentException().isThrownBy(() -> config.checkOrigin("https://domain.com"));
+
+ config.addAllowedOriginPattern("https://*.domain.com");
assertThat(config.checkOrigin("https://example.domain.com")).isEqualTo("https://example.domain.com");
+
config.setAllowCredentials(false);
assertThat(config.checkOrigin("https://example.domain.com")).isEqualTo("https://example.domain.com");
}
@@ -317,10 +338,12 @@ public class CorsConfigurationTests {
CorsConfiguration config = new CorsConfiguration();
assertThat(config.checkOrigin(null)).isNull();
assertThat(config.checkOrigin("https://domain.com")).isNull();
- config.addAllowedOriginPattern(".*");
+ config.addAllowedOriginPattern("*");
assertThat(config.checkOrigin(null)).isNull();
- config.setAllowedOriginPatterns(Collections.singletonList(".*\\.domain1\\.com"));
+
+ config.setAllowedOriginPatterns(Collections.singletonList("http://*.domain1.com"));
assertThat(config.checkOrigin("https://domain2.com")).isNull();
+
config.setAllowedOriginPatterns(new ArrayList<>());
assertThat(config.checkOrigin("https://domain.com")).isNull();
}
@@ -329,8 +352,10 @@ public class CorsConfigurationTests {
public void checkMethodAllowed() {
CorsConfiguration config = new CorsConfiguration();
assertThat(config.checkHttpMethod(HttpMethod.GET)).containsExactly(HttpMethod.GET, HttpMethod.HEAD);
+
config.addAllowedMethod("GET");
assertThat(config.checkHttpMethod(HttpMethod.GET)).containsExactly(HttpMethod.GET);
+
config.addAllowedMethod("POST");
assertThat(config.checkHttpMethod(HttpMethod.GET)).containsExactly(HttpMethod.GET, HttpMethod.POST);
assertThat(config.checkHttpMethod(HttpMethod.POST)).containsExactly(HttpMethod.GET, HttpMethod.POST);
@@ -341,6 +366,7 @@ public class CorsConfigurationTests {
CorsConfiguration config = new CorsConfiguration();
assertThat(config.checkHttpMethod(null)).isNull();
assertThat(config.checkHttpMethod(HttpMethod.DELETE)).isNull();
+
config.setAllowedMethods(new ArrayList<>());
assertThat(config.checkHttpMethod(HttpMethod.POST)).isNull();
}
@@ -349,8 +375,10 @@ public class CorsConfigurationTests {
public void checkHeadersAllowed() {
CorsConfiguration config = new CorsConfiguration();
assertThat(config.checkHeaders(Collections.emptyList())).isEqualTo(Collections.emptyList());
+
config.addAllowedHeader("header1");
config.addAllowedHeader("header2");
+
assertThat(config.checkHeaders(Collections.singletonList("header1"))).containsExactly("header1");
assertThat(config.checkHeaders(Arrays.asList("header1", "header2"))).containsExactly("header1", "header2");
assertThat(config.checkHeaders(Arrays.asList("header1", "header2", "header3"))).containsExactly("header1", "header2");
@@ -361,8 +389,10 @@ public class CorsConfigurationTests {
CorsConfiguration config = new CorsConfiguration();
assertThat(config.checkHeaders(null)).isNull();
assertThat(config.checkHeaders(Collections.singletonList("header1"))).isNull();
+
config.setAllowedHeaders(Collections.emptyList());
assertThat(config.checkHeaders(Collections.singletonList("header1"))).isNull();
+
config.addAllowedHeader("header2");
config.addAllowedHeader("header3");
assertThat(config.checkHeaders(Collections.singletonList("header1"))).isNull();
@@ -374,6 +404,7 @@ public class CorsConfigurationTests {
config.addAllowedOrigin("https://domain.com");
config.addAllowedHeader("header1");
config.addAllowedMethod("PATCH");
+
assertThat(config.getAllowedOrigins()).containsExactly("*", "https://domain.com");
assertThat(config.getAllowedHeaders()).containsExactly("*", "header1");
assertThat(config.getAllowedMethods()).containsExactly("GET", "HEAD", "POST", "PATCH");
@@ -382,9 +413,10 @@ public class CorsConfigurationTests {
@Test
public void permitDefaultDoesntSetOriginWhenPatternPresent() {
CorsConfiguration config = new CorsConfiguration();
- config.addAllowedOriginPattern(".*\\.com");
+ config.addAllowedOriginPattern("http://*.com");
config = config.applyPermitDefaultValues();
+
assertThat(config.getAllowedOrigins()).isNull();
- assertThat(config.getAllowedOriginPatterns()).containsExactly(".*\\.com");
+ assertThat(config.getAllowedOriginPatterns()).containsExactly("http://*.com");
}
}
diff --git a/spring-web/src/test/java/org/springframework/web/cors/DefaultCorsProcessorTests.java b/spring-web/src/test/java/org/springframework/web/cors/DefaultCorsProcessorTests.java
index 1945509603f..5c163779723 100644
--- a/spring-web/src/test/java/org/springframework/web/cors/DefaultCorsProcessorTests.java
+++ b/spring-web/src/test/java/org/springframework/web/cors/DefaultCorsProcessorTests.java
@@ -16,6 +16,8 @@
package org.springframework.web.cors;
+import java.util.Arrays;
+
import javax.servlet.http.HttpServletResponse;
import org.junit.jupiter.api.BeforeEach;
@@ -27,6 +29,7 @@ import org.springframework.web.testfixture.servlet.MockHttpServletRequest;
import org.springframework.web.testfixture.servlet.MockHttpServletResponse;
import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
/**
* Test {@link DefaultCorsProcessor} with simple or preflight CORS request.
@@ -138,11 +141,17 @@ public class DefaultCorsProcessorTests {
}
@Test
- public void actualRequestCredentialsWithOriginWildcard() throws Exception {
+ public void actualRequestCredentialsWithWildcardOrigin() throws Exception {
this.request.setMethod(HttpMethod.GET.name());
this.request.addHeader(HttpHeaders.ORIGIN, "https://domain2.com");
+
this.conf.addAllowedOrigin("*");
this.conf.setAllowCredentials(true);
+ assertThatIllegalArgumentException()
+ .isThrownBy(() -> this.processor.processRequest(this.conf, this.request, this.response));
+
+ this.conf.setAllowedOrigins(null);
+ this.conf.addAllowedOriginPattern("*");
this.processor.processRequest(this.conf, this.request, this.response);
assertThat(this.response.containsHeader(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN)).isTrue();
@@ -311,17 +320,21 @@ public class DefaultCorsProcessorTests {
}
@Test
- public void preflightRequestCredentialsWithOriginWildcard() throws Exception {
+ public void preflightRequestCredentialsWithWildcardOrigin() throws Exception {
this.request.setMethod(HttpMethod.OPTIONS.name());
this.request.addHeader(HttpHeaders.ORIGIN, "https://domain2.com");
this.request.addHeader(HttpHeaders.ACCESS_CONTROL_REQUEST_METHOD, "GET");
this.request.addHeader(HttpHeaders.ACCESS_CONTROL_REQUEST_HEADERS, "Header1");
- this.conf.addAllowedOrigin("https://domain1.com");
- this.conf.addAllowedOrigin("*");
- this.conf.addAllowedOrigin("http://domain3.example");
+ this.conf.setAllowedOrigins(Arrays.asList("https://domain1.com", "*", "http://domain3.example"));
this.conf.addAllowedHeader("Header1");
this.conf.setAllowCredentials(true);
+ assertThatIllegalArgumentException().isThrownBy(() ->
+ this.processor.processRequest(this.conf, this.request, this.response));
+
+ this.conf.setAllowedOrigins(null);
+ this.conf.addAllowedOriginPattern("*");
+
this.processor.processRequest(this.conf, this.request, this.response);
assertThat(this.response.containsHeader(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN)).isTrue();
assertThat(this.response.getHeader(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN)).isEqualTo("https://domain2.com");
diff --git a/spring-web/src/test/java/org/springframework/web/cors/reactive/DefaultCorsProcessorTests.java b/spring-web/src/test/java/org/springframework/web/cors/reactive/DefaultCorsProcessorTests.java
index 1360430d6e4..4549d1409a7 100644
--- a/spring-web/src/test/java/org/springframework/web/cors/reactive/DefaultCorsProcessorTests.java
+++ b/spring-web/src/test/java/org/springframework/web/cors/reactive/DefaultCorsProcessorTests.java
@@ -29,6 +29,7 @@ import org.springframework.web.testfixture.http.server.reactive.MockServerHttpRe
import org.springframework.web.testfixture.server.MockServerWebExchange;
import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
import static org.springframework.http.HttpHeaders.ACCESS_CONTROL_ALLOW_HEADERS;
import static org.springframework.http.HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN;
import static org.springframework.http.HttpHeaders.ACCESS_CONTROL_REQUEST_HEADERS;
@@ -56,7 +57,7 @@ public class DefaultCorsProcessorTests {
@Test
- public void requestWithoutOriginHeader() throws Exception {
+ public void requestWithoutOriginHeader() {
MockServerHttpRequest request = MockServerHttpRequest
.method(HttpMethod.GET, "http://domain1.example/test.html")
.build();
@@ -71,7 +72,7 @@ public class DefaultCorsProcessorTests {
}
@Test
- public void sameOriginRequest() throws Exception {
+ public void sameOriginRequest() {
MockServerHttpRequest request = MockServerHttpRequest
.method(HttpMethod.GET, "http://domain1.example/test.html")
.header(HttpHeaders.ORIGIN, "http://domain1.example")
@@ -87,7 +88,7 @@ public class DefaultCorsProcessorTests {
}
@Test
- public void actualRequestWithOriginHeader() throws Exception {
+ public void actualRequestWithOriginHeader() {
ServerWebExchange exchange = actualRequest();
this.processor.process(this.conf, exchange);
@@ -99,7 +100,7 @@ public class DefaultCorsProcessorTests {
}
@Test
- public void actualRequestWithOriginHeaderAndNullConfig() throws Exception {
+ public void actualRequestWithOriginHeaderAndNullConfig() {
ServerWebExchange exchange = actualRequest();
this.processor.process(null, exchange);
@@ -109,7 +110,7 @@ public class DefaultCorsProcessorTests {
}
@Test
- public void actualRequestWithOriginHeaderAndAllowedOrigin() throws Exception {
+ public void actualRequestWithOriginHeaderAndAllowedOrigin() {
ServerWebExchange exchange = actualRequest();
this.conf.addAllowedOrigin("*");
this.processor.process(this.conf, exchange);
@@ -125,7 +126,7 @@ public class DefaultCorsProcessorTests {
}
@Test
- public void actualRequestCredentials() throws Exception {
+ public void actualRequestCredentials() {
ServerWebExchange exchange = actualRequest();
this.conf.addAllowedOrigin("https://domain1.com");
this.conf.addAllowedOrigin("https://domain2.com");
@@ -144,10 +145,14 @@ public class DefaultCorsProcessorTests {
}
@Test
- public void actualRequestCredentialsWithOriginWildcard() throws Exception {
+ public void actualRequestCredentialsWithWildcardOrigin() {
ServerWebExchange exchange = actualRequest();
this.conf.addAllowedOrigin("*");
this.conf.setAllowCredentials(true);
+ assertThatIllegalArgumentException().isThrownBy(() -> this.processor.process(this.conf, exchange));
+
+ this.conf.setAllowedOrigins(null);
+ this.conf.addAllowedOriginPattern("*");
this.processor.process(this.conf, exchange);
ServerHttpResponse response = exchange.getResponse();
@@ -161,7 +166,7 @@ public class DefaultCorsProcessorTests {
}
@Test
- public void actualRequestCaseInsensitiveOriginMatch() throws Exception {
+ public void actualRequestCaseInsensitiveOriginMatch() {
ServerWebExchange exchange = actualRequest();
this.conf.addAllowedOrigin("https://DOMAIN2.com");
this.processor.process(this.conf, exchange);
@@ -174,7 +179,7 @@ public class DefaultCorsProcessorTests {
}
@Test
- public void actualRequestExposedHeaders() throws Exception {
+ public void actualRequestExposedHeaders() {
ServerWebExchange exchange = actualRequest();
this.conf.addExposedHeader("header1");
this.conf.addExposedHeader("header2");
@@ -193,7 +198,7 @@ public class DefaultCorsProcessorTests {
}
@Test
- public void preflightRequestAllOriginsAllowed() throws Exception {
+ public void preflightRequestAllOriginsAllowed() {
ServerWebExchange exchange = MockServerWebExchange.from(
preFlightRequest().header(ACCESS_CONTROL_REQUEST_METHOD, "GET"));
this.conf.addAllowedOrigin("*");
@@ -207,7 +212,7 @@ public class DefaultCorsProcessorTests {
@Test
- public void preflightRequestWrongAllowedMethod() throws Exception {
+ public void preflightRequestWrongAllowedMethod() {
ServerWebExchange exchange = MockServerWebExchange.from(
preFlightRequest().header(ACCESS_CONTROL_REQUEST_METHOD, "DELETE"));
this.conf.addAllowedOrigin("*");
@@ -220,7 +225,7 @@ public class DefaultCorsProcessorTests {
}
@Test
- public void preflightRequestMatchedAllowedMethod() throws Exception {
+ public void preflightRequestMatchedAllowedMethod() {
ServerWebExchange exchange = MockServerWebExchange.from(
preFlightRequest().header(ACCESS_CONTROL_REQUEST_METHOD, "GET"));
this.conf.addAllowedOrigin("*");
@@ -234,7 +239,7 @@ public class DefaultCorsProcessorTests {
}
@Test
- public void preflightRequestTestWithOriginButWithoutOtherHeaders() throws Exception {
+ public void preflightRequestTestWithOriginButWithoutOtherHeaders() {
ServerWebExchange exchange = MockServerWebExchange.from(preFlightRequest());
this.processor.process(this.conf, exchange);
@@ -246,7 +251,7 @@ public class DefaultCorsProcessorTests {
}
@Test
- public void preflightRequestWithoutRequestMethod() throws Exception {
+ public void preflightRequestWithoutRequestMethod() {
ServerWebExchange exchange = MockServerWebExchange.from(
preFlightRequest().header(ACCESS_CONTROL_REQUEST_HEADERS, "Header1"));
this.processor.process(this.conf, exchange);
@@ -259,7 +264,7 @@ public class DefaultCorsProcessorTests {
}
@Test
- public void preflightRequestWithRequestAndMethodHeaderButNoConfig() throws Exception {
+ public void preflightRequestWithRequestAndMethodHeaderButNoConfig() {
ServerWebExchange exchange = MockServerWebExchange.from(preFlightRequest()
.header(ACCESS_CONTROL_REQUEST_METHOD, "GET")
.header(ACCESS_CONTROL_REQUEST_HEADERS, "Header1"));
@@ -274,7 +279,7 @@ public class DefaultCorsProcessorTests {
}
@Test
- public void preflightRequestValidRequestAndConfig() throws Exception {
+ public void preflightRequestValidRequestAndConfig() {
ServerWebExchange exchange = MockServerWebExchange.from(preFlightRequest()
.header(ACCESS_CONTROL_REQUEST_METHOD, "GET")
.header(ACCESS_CONTROL_REQUEST_HEADERS, "Header1"));
@@ -299,7 +304,7 @@ public class DefaultCorsProcessorTests {
}
@Test
- public void preflightRequestCredentials() throws Exception {
+ public void preflightRequestCredentials() {
ServerWebExchange exchange = MockServerWebExchange.from(preFlightRequest()
.header(ACCESS_CONTROL_REQUEST_METHOD, "GET")
.header(ACCESS_CONTROL_REQUEST_HEADERS, "Header1"));
@@ -323,7 +328,7 @@ public class DefaultCorsProcessorTests {
}
@Test
- public void preflightRequestCredentialsWithOriginWildcard() throws Exception {
+ public void preflightRequestCredentialsWithWildcardOrigin() {
ServerWebExchange exchange = MockServerWebExchange.from(preFlightRequest()
.header(ACCESS_CONTROL_REQUEST_METHOD, "GET")
.header(ACCESS_CONTROL_REQUEST_HEADERS, "Header1"));
@@ -333,7 +338,10 @@ public class DefaultCorsProcessorTests {
this.conf.addAllowedOrigin("http://domain3.example");
this.conf.addAllowedHeader("Header1");
this.conf.setAllowCredentials(true);
+ assertThatIllegalArgumentException().isThrownBy(() -> this.processor.process(this.conf, exchange));
+ this.conf.setAllowedOrigins(null);
+ this.conf.addAllowedOriginPattern("*");
this.processor.process(this.conf, exchange);
ServerHttpResponse response = exchange.getResponse();
@@ -345,7 +353,7 @@ public class DefaultCorsProcessorTests {
}
@Test
- public void preflightRequestAllowedHeaders() throws Exception {
+ public void preflightRequestAllowedHeaders() {
ServerWebExchange exchange = MockServerWebExchange.from(preFlightRequest()
.header(ACCESS_CONTROL_REQUEST_METHOD, "GET")
.header(ACCESS_CONTROL_REQUEST_HEADERS, "Header1, Header2"));
@@ -369,7 +377,7 @@ public class DefaultCorsProcessorTests {
}
@Test
- public void preflightRequestAllowsAllHeaders() throws Exception {
+ public void preflightRequestAllowsAllHeaders() {
ServerWebExchange exchange = MockServerWebExchange.from(preFlightRequest()
.header(ACCESS_CONTROL_REQUEST_METHOD, "GET")
.header(ACCESS_CONTROL_REQUEST_HEADERS, "Header1, Header2"));
@@ -391,7 +399,7 @@ public class DefaultCorsProcessorTests {
}
@Test
- public void preflightRequestWithEmptyHeaders() throws Exception {
+ public void preflightRequestWithEmptyHeaders() {
ServerWebExchange exchange = MockServerWebExchange.from(preFlightRequest()
.header(ACCESS_CONTROL_REQUEST_METHOD, "GET")
.header(ACCESS_CONTROL_REQUEST_HEADERS, ""));
diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/config/CorsRegistration.java b/spring-webflux/src/main/java/org/springframework/web/reactive/config/CorsRegistration.java
index 8dad2ae07fc..44fbd59986a 100644
--- a/spring-webflux/src/main/java/org/springframework/web/reactive/config/CorsRegistration.java
+++ b/spring-webflux/src/main/java/org/springframework/web/reactive/config/CorsRegistration.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2018 the original author or authors.
+ * Copyright 2002-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -18,6 +18,7 @@ package org.springframework.web.reactive.config;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.List;
import org.springframework.web.cors.CorsConfiguration;
@@ -44,24 +45,28 @@ public class CorsRegistration {
/**
- * The list of allowed origins that be specific origins, e.g.
- * {@code "https://domain1.com"}, or {@code "*"} for all origins.
- * A matched origin is listed in the {@code Access-Control-Allow-Origin}
- * response header of preflight actual CORS requests.
- *
By default all origins are allowed.
- *
Note: CORS checks use values from "Forwarded"
- * (RFC 7239),
- * "X-Forwarded-Host", "X-Forwarded-Port", and "X-Forwarded-Proto" headers,
- * if present, in order to reflect the client-originated address.
- * Consider using the {@code ForwardedHeaderFilter} in order to choose from a
- * central place whether to extract and use, or to discard such headers.
- * See the Spring Framework reference for more on this filter.
+ * A list of origins for which cross-origin requests are allowed. Please,
+ * see {@link CorsConfiguration#setAllowedOrigins(List)} for details.
+ *
By default all origins are allowed unless {@code originPatterns} is
+ * also set in which case {@code originPatterns} is used instead.
*/
public CorsRegistration allowedOrigins(String... origins) {
this.config.setAllowedOrigins(new ArrayList<>(Arrays.asList(origins)));
return this;
}
+ /**
+ * Alternative to {@link #allowCredentials} that supports origins declared
+ * via wildcard patterns. Please, see
+ * @link CorsConfiguration#setAllowedOriginPatterns(List)} for details.
+ *
By default this is not set.
+ * @since 5.3
+ */
+ public CorsRegistration allowedOriginPatterns(String... patterns) {
+ this.config.setAllowedOriginPatterns(Arrays.asList(patterns));
+ return this;
+ }
+
/**
* Set the HTTP methods to allow, e.g. {@code "GET"}, {@code "POST"}, etc.
*
The special value {@code "*"} allows all methods.
diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/handler/AbstractHandlerMapping.java b/spring-webflux/src/main/java/org/springframework/web/reactive/handler/AbstractHandlerMapping.java
index b3311348a67..0c4464d3aef 100644
--- a/spring-webflux/src/main/java/org/springframework/web/reactive/handler/AbstractHandlerMapping.java
+++ b/spring-webflux/src/main/java/org/springframework/web/reactive/handler/AbstractHandlerMapping.java
@@ -187,6 +187,9 @@ public abstract class AbstractHandlerMapping extends ApplicationObjectSupport
CorsConfiguration config = (this.corsConfigurationSource != null ? this.corsConfigurationSource.getCorsConfiguration(exchange) : null);
CorsConfiguration handlerConfig = getCorsConfiguration(handler, exchange);
config = (config != null ? config.combine(handlerConfig) : handlerConfig);
+ if (config != null) {
+ config.validateAllowCredentials();
+ }
if (!this.corsProcessor.process(config, exchange) || CorsUtils.isPreFlightRequest(request)) {
return REQUEST_HANDLED_HANDLER;
}
diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/AbstractHandlerMethodMapping.java b/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/AbstractHandlerMethodMapping.java
index c7a65fc6236..163f220a5c6 100644
--- a/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/AbstractHandlerMethodMapping.java
+++ b/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/AbstractHandlerMethodMapping.java
@@ -87,7 +87,7 @@ public abstract class AbstractHandlerMethodMapping extends AbstractHandlerMap
private static final CorsConfiguration ALLOW_CORS_CONFIG = new CorsConfiguration();
static {
- ALLOW_CORS_CONFIG.addAllowedOrigin("*");
+ ALLOW_CORS_CONFIG.addAllowedOriginPattern("*");
ALLOW_CORS_CONFIG.addAllowedMethod("*");
ALLOW_CORS_CONFIG.addAllowedHeader("*");
ALLOW_CORS_CONFIG.setAllowCredentials(true);
@@ -485,9 +485,10 @@ public abstract class AbstractHandlerMethodMapping extends AbstractHandlerMap
validateMethodMapping(handlerMethod, mapping);
this.mappingLookup.put(mapping, handlerMethod);
- CorsConfiguration corsConfig = initCorsConfiguration(handler, method, mapping);
- if (corsConfig != null) {
- this.corsLookup.put(handlerMethod, corsConfig);
+ CorsConfiguration config = initCorsConfiguration(handler, method, mapping);
+ if (config != null) {
+ config.validateAllowCredentials();
+ this.corsLookup.put(handlerMethod, config);
}
this.registry.put(mapping, new MappingRegistration<>(mapping, handlerMethod));
diff --git a/spring-webflux/src/test/java/org/springframework/web/reactive/config/CorsRegistryTests.java b/spring-webflux/src/test/java/org/springframework/web/reactive/config/CorsRegistryTests.java
index d104d217677..b4dc68898ff 100644
--- a/spring-webflux/src/test/java/org/springframework/web/reactive/config/CorsRegistryTests.java
+++ b/spring-webflux/src/test/java/org/springframework/web/reactive/config/CorsRegistryTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2019 the original author or authors.
+ * Copyright 2002-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -17,6 +17,7 @@
package org.springframework.web.reactive.config;
import java.util.Arrays;
+import java.util.Collections;
import java.util.Map;
import org.junit.jupiter.api.Test;
@@ -56,11 +57,20 @@ public class CorsRegistryTests {
assertThat(configs.size()).isEqualTo(1);
CorsConfiguration config = configs.get("/foo");
assertThat(config.getAllowedOrigins()).isEqualTo(Arrays.asList("https://domain2.com", "https://domain2.com"));
- assertThat(config.getAllowedMethods()).isEqualTo(Arrays.asList("DELETE"));
+ assertThat(config.getAllowedMethods()).isEqualTo(Collections.singletonList("DELETE"));
assertThat(config.getAllowedHeaders()).isEqualTo(Arrays.asList("header1", "header2"));
assertThat(config.getExposedHeaders()).isEqualTo(Arrays.asList("header3", "header4"));
assertThat(config.getAllowCredentials()).isEqualTo(false);
assertThat(config.getMaxAge()).isEqualTo(Long.valueOf(3600));
}
+ @Test
+ public void allowCredentials() {
+ this.registry.addMapping("/foo").allowCredentials(true);
+ CorsConfiguration config = this.registry.getCorsConfigurations().get("/foo");
+ assertThat(config.getAllowedOrigins())
+ .as("Globally origins=\"*\" and allowCredentials=true should be possible")
+ .containsExactly("*");
+ }
+
}
diff --git a/spring-webflux/src/test/java/org/springframework/web/reactive/handler/CorsUrlHandlerMappingTests.java b/spring-webflux/src/test/java/org/springframework/web/reactive/handler/CorsUrlHandlerMappingTests.java
index 3c7b1b7d3f5..7f10816c408 100644
--- a/spring-webflux/src/test/java/org/springframework/web/reactive/handler/CorsUrlHandlerMappingTests.java
+++ b/spring-webflux/src/test/java/org/springframework/web/reactive/handler/CorsUrlHandlerMappingTests.java
@@ -113,7 +113,7 @@ public class CorsUrlHandlerMappingTests {
@Test
public void actualRequestWithGlobalPatternCorsConfig() throws Exception {
CorsConfiguration mappedConfig = new CorsConfiguration();
- mappedConfig.addAllowedOriginPattern(".*\\.domain2.com");
+ mappedConfig.addAllowedOriginPattern("https://*.domain2.com");
this.handlerMapping.setCorsConfigurations(Collections.singletonMap("/welcome.html", mappedConfig));
String origin = "https://example.domain2.com";
@@ -122,7 +122,8 @@ public class CorsUrlHandlerMappingTests {
assertThat(actual).isNotNull();
assertThat(actual).isSameAs(this.welcomeController);
- assertThat(exchange.getResponse().getHeaders().getFirst(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN)).isEqualTo("https://example.domain2.com");
+ assertThat(exchange.getResponse().getHeaders().getFirst(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN))
+ .isEqualTo("https://example.domain2.com");
}
@Test
@@ -197,7 +198,7 @@ public class CorsUrlHandlerMappingTests {
@Override
public CorsConfiguration getCorsConfiguration(ServerWebExchange exchange) {
CorsConfiguration config = new CorsConfiguration();
- config.addAllowedOrigin("*");
+ config.addAllowedOriginPattern("*");
config.setAllowCredentials(true);
return config;
}
diff --git a/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/annotation/CrossOriginAnnotationIntegrationTests.java b/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/annotation/CrossOriginAnnotationIntegrationTests.java
index a5439a4e2a2..aa7f287a4a2 100644
--- a/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/annotation/CrossOriginAnnotationIntegrationTests.java
+++ b/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/annotation/CrossOriginAnnotationIntegrationTests.java
@@ -68,7 +68,7 @@ class CrossOriginAnnotationIntegrationTests extends AbstractRequestMappingIntegr
context.register(WebConfig.class);
Properties props = new Properties();
props.setProperty("myOrigin", "https://site1.com");
- props.setProperty("myOriginPattern", ".*\\.com");
+ props.setProperty("myOriginPattern", "https://*.com");
context.getEnvironment().getPropertySources().addFirst(new PropertiesPropertySource("ps", props));
context.register(PropertySourcesPlaceholderConfigurer.class);
context.refresh();
@@ -358,7 +358,7 @@ class CrossOriginAnnotationIntegrationTests extends AbstractRequestMappingIntegr
return "placeholder";
}
- @CrossOrigin(originPatterns = ".*\\.com")
+ @CrossOrigin(originPatterns = "https://*.com")
@GetMapping("/origin-pattern-value-attribute")
public String customOriginPatternDefinedViaValueAttribute() {
return "pattern-value-attribute";
@@ -388,7 +388,7 @@ class CrossOriginAnnotationIntegrationTests extends AbstractRequestMappingIntegr
return "bar";
}
- @CrossOrigin(allowCredentials = "true")
+ @CrossOrigin(originPatterns = "*", allowCredentials = "true")
@GetMapping("/baz")
public String baz() {
return "baz";
diff --git a/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/annotation/GlobalCorsConfigIntegrationTests.java b/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/annotation/GlobalCorsConfigIntegrationTests.java
index e3a1555b9f6..3c59e7e75c1 100644
--- a/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/annotation/GlobalCorsConfigIntegrationTests.java
+++ b/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/annotation/GlobalCorsConfigIntegrationTests.java
@@ -161,8 +161,7 @@ class GlobalCorsConfigIntegrationTests extends AbstractRequestMappingIntegration
ResponseEntity entity = performOptions("/ambiguous", this.headers, String.class);
assertThat(entity.getStatusCode()).isEqualTo(HttpStatus.OK);
assertThat(entity.getHeaders().getAccessControlAllowOrigin()).isEqualTo("http://localhost:9000");
- assertThat(entity.getHeaders().getAccessControlAllowMethods())
- .containsExactly(HttpMethod.GET);
+ assertThat(entity.getHeaders().getAccessControlAllowMethods()).containsExactly(HttpMethod.GET);
assertThat(entity.getHeaders().getAccessControlAllowCredentials()).isEqualTo(true);
assertThat(entity.getHeaders().get(HttpHeaders.VARY))
.containsExactly(HttpHeaders.ORIGIN, HttpHeaders.ACCESS_CONTROL_REQUEST_METHOD,
@@ -177,12 +176,9 @@ class GlobalCorsConfigIntegrationTests extends AbstractRequestMappingIntegration
@Override
protected void addCorsMappings(CorsRegistry registry) {
- registry.addMapping("/cors-restricted")
- .allowedOrigins("https://foo")
- .allowedMethods("GET", "POST");
+ registry.addMapping("/cors-restricted").allowedOrigins("https://foo").allowedMethods("GET", "POST");
registry.addMapping("/cors");
- registry.addMapping("/ambiguous")
- .allowedMethods("GET", "POST");
+ registry.addMapping("/ambiguous").allowedMethods("GET", "POST");
}
}
diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/config/CorsBeanDefinitionParser.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/config/CorsBeanDefinitionParser.java
index d4dfdbb117b..20997963fe6 100644
--- a/spring-webmvc/src/main/java/org/springframework/web/servlet/config/CorsBeanDefinitionParser.java
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/config/CorsBeanDefinitionParser.java
@@ -60,6 +60,10 @@ public class CorsBeanDefinitionParser implements BeanDefinitionParser {
String[] allowedOrigins = StringUtils.tokenizeToStringArray(mapping.getAttribute("allowed-origins"), ",");
config.setAllowedOrigins(Arrays.asList(allowedOrigins));
}
+ if (mapping.hasAttribute("allowed-origin-patterns")) {
+ String[] patterns = StringUtils.tokenizeToStringArray(mapping.getAttribute("allowed-origin-patterns"), ",");
+ config.setAllowedOriginPatterns(Arrays.asList(patterns));
+ }
if (mapping.hasAttribute("allowed-methods")) {
String[] allowedMethods = StringUtils.tokenizeToStringArray(mapping.getAttribute("allowed-methods"), ",");
config.setAllowedMethods(Arrays.asList(allowedMethods));
@@ -78,7 +82,9 @@ public class CorsBeanDefinitionParser implements BeanDefinitionParser {
if (mapping.hasAttribute("max-age")) {
config.setMaxAge(Long.parseLong(mapping.getAttribute("max-age")));
}
- corsConfigurations.put(mapping.getAttribute("path"), config.applyPermitDefaultValues());
+ config.applyPermitDefaultValues();
+ config.validateAllowCredentials();
+ corsConfigurations.put(mapping.getAttribute("path"), config);
}
}
diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/config/annotation/CorsRegistration.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/config/annotation/CorsRegistration.java
index cf641d3218f..3e008e76bed 100644
--- a/spring-webmvc/src/main/java/org/springframework/web/servlet/config/annotation/CorsRegistration.java
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/config/annotation/CorsRegistration.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2018 the original author or authors.
+ * Copyright 2002-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -17,6 +17,7 @@
package org.springframework.web.servlet.config.annotation;
import java.util.Arrays;
+import java.util.List;
import org.springframework.web.cors.CorsConfiguration;
@@ -46,24 +47,27 @@ public class CorsRegistration {
/**
- * The list of allowed origins that be specific origins, e.g.
- * {@code "https://domain1.com"}, or {@code "*"} for all origins.
- * A matched origin is listed in the {@code Access-Control-Allow-Origin}
- * response header of preflight actual CORS requests.
- *
By default, all origins are allowed.
- *
Note: CORS checks use values from "Forwarded"
- * (RFC 7239),
- * "X-Forwarded-Host", "X-Forwarded-Port", and "X-Forwarded-Proto" headers,
- * if present, in order to reflect the client-originated address.
- * Consider using the {@code ForwardedHeaderFilter} in order to choose from a
- * central place whether to extract and use, or to discard such headers.
- * See the Spring Framework reference for more on this filter.
+ * A list of origins for which cross-origin requests are allowed. Please,
+ * see {@link CorsConfiguration#setAllowedOrigins(List)} for details.
+ *
By default all origins are allowed unless {@code originPatterns} is
+ * also set in which case {@code originPatterns} is used instead.
*/
public CorsRegistration allowedOrigins(String... origins) {
this.config.setAllowedOrigins(Arrays.asList(origins));
return this;
}
+ /**
+ * Alternative to {@link #allowCredentials} that supports origins declared
+ * via wildcard patterns. Please, see
+ * @link CorsConfiguration#setAllowedOriginPatterns(List)} for details.
+ *
By default this is not set.
+ * @since 5.3
+ */
+ public CorsRegistration allowedOriginPatterns(String... patterns) {
+ this.config.setAllowedOriginPatterns(Arrays.asList(patterns));
+ return this;
+ }
/**
* Set the HTTP methods to allow, e.g. {@code "GET"}, {@code "POST"}, etc.
diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/handler/AbstractHandlerMapping.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/handler/AbstractHandlerMapping.java
index 07328f1d8cc..fe55060172a 100644
--- a/spring-webmvc/src/main/java/org/springframework/web/servlet/handler/AbstractHandlerMapping.java
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/handler/AbstractHandlerMapping.java
@@ -516,6 +516,9 @@ public abstract class AbstractHandlerMapping extends WebApplicationObjectSupport
CorsConfiguration globalConfig = getCorsConfigurationSource().getCorsConfiguration(request);
config = (globalConfig != null ? globalConfig.combine(config) : config);
}
+ if (config != null) {
+ config.validateAllowCredentials();
+ }
executionChain = getCorsHandlerExecutionChain(request, executionChain, config);
}
diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/handler/AbstractHandlerMethodMapping.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/handler/AbstractHandlerMethodMapping.java
index e352e047229..c2eb2710285 100644
--- a/spring-webmvc/src/main/java/org/springframework/web/servlet/handler/AbstractHandlerMethodMapping.java
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/handler/AbstractHandlerMethodMapping.java
@@ -86,7 +86,7 @@ public abstract class AbstractHandlerMethodMapping extends AbstractHandlerMap
private static final CorsConfiguration ALLOW_CORS_CONFIG = new CorsConfiguration();
static {
- ALLOW_CORS_CONFIG.addAllowedOrigin("*");
+ ALLOW_CORS_CONFIG.addAllowedOriginPattern("*");
ALLOW_CORS_CONFIG.addAllowedMethod("*");
ALLOW_CORS_CONFIG.addAllowedHeader("*");
ALLOW_CORS_CONFIG.setAllowCredentials(true);
@@ -630,9 +630,10 @@ public abstract class AbstractHandlerMethodMapping extends AbstractHandlerMap
addMappingName(name, handlerMethod);
}
- CorsConfiguration corsConfig = initCorsConfiguration(handler, method, mapping);
- if (corsConfig != null) {
- this.corsLookup.put(handlerMethod, corsConfig);
+ CorsConfiguration config = initCorsConfiguration(handler, method, mapping);
+ if (config != null) {
+ config.validateAllowCredentials();
+ this.corsLookup.put(handlerMethod, config);
}
this.registry.put(mapping, new MappingRegistration<>(mapping, handlerMethod, directUrls, name));
diff --git a/spring-webmvc/src/main/resources/org/springframework/web/servlet/config/spring-mvc.xsd b/spring-webmvc/src/main/resources/org/springframework/web/servlet/config/spring-mvc.xsd
index 0c0671c0c01..645fc09e3f5 100644
--- a/spring-webmvc/src/main/resources/org/springframework/web/servlet/config/spring-mvc.xsd
+++ b/spring-webmvc/src/main/resources/org/springframework/web/servlet/config/spring-mvc.xsd
@@ -1346,6 +1346,15 @@
Comma-separated list of origins to allow, e.g. "https://domain1.com, https://domain2.com".
The special value "*" allows all domains (default).
+ For matching pre-flight and actual requests the "Access-Control-Allow-Origin"
+ response header is set either to the matched domain value or to "*".
+ Keep in mind however that the CORS spec does not allow "*" when allow-credentials
+ is set to true and that is rejected as of 5.3. See allowed-origin-patterns for
+ further options.
+
+ By default all origins are allowed unless allowed-origin-patterns is also set
+ in which case allowed-origin-patterns is used instead.
+
Note that CORS checks use values from "Forwarded" (RFC 7239), "X-Forwarded-Host",
"X-Forwarded-Port", and "X-Forwarded-Proto" headers, if present, in order to reflect
the client-originated address. Consider using the ForwardedHeaderFilter in order to
@@ -1354,6 +1363,20 @@
]]>
+
+
+
+
+
- mapping.registerHandler(new MethodLevelControllerWithBogusAllowCredentialsValue()))
- .withMessageContaining("@CrossOrigin's allowCredentials")
- .withMessageContaining("current value is [bogus]");
+ assertThatIllegalStateException()
+ .isThrownBy(() -> mapping.registerHandler(new MethodLevelControllerWithBogusAllowCredentialsValue()))
+ .withMessageContaining("@CrossOrigin's allowCredentials")
+ .withMessageContaining("current value is [bogus]");
+ }
+
+ @PathPatternsParameterizedTest
+ void allowCredentialsWithDefaultOrigin(TestRequestMappingInfoHandlerMapping mapping) {
+ assertThatIllegalArgumentException()
+ .isThrownBy(() -> mapping.registerHandler(new CredentialsWithDefaultOriginController()))
+ .withMessageContaining("When allowCredentials is true, allowedOrigins cannot contain");
+ }
+
+ @PathPatternsParameterizedTest
+ void allowCredentialsWithWildcardOrigin(TestRequestMappingInfoHandlerMapping mapping) {
+ assertThatIllegalArgumentException()
+ .isThrownBy(() -> mapping.registerHandler(new CredentialsWithWildcardOriginController()))
+ .withMessageContaining("When allowCredentials is true, allowedOrigins cannot contain");
}
@PathPatternsParameterizedTest
@@ -255,7 +270,8 @@ class CrossOriginTests {
config = getCorsConfiguration(chain, false);
assertThat(config).isNotNull();
assertThat(config.getAllowedMethods()).containsExactly("GET");
- assertThat(config.getAllowedOrigins()).containsExactly("*");
+ assertThat(config.getAllowedOrigins()).isNull();
+ assertThat(config.getAllowedOriginPatterns()).containsExactly("*");
assertThat(config.getAllowCredentials()).isTrue();
}
@@ -313,7 +329,8 @@ class CrossOriginTests {
CorsConfiguration config = getCorsConfiguration(chain, true);
assertThat(config).isNotNull();
assertThat(config.getAllowedMethods()).containsExactly("*");
- assertThat(config.getAllowedOrigins()).containsExactly("*");
+ assertThat(config.getAllowedOrigins()).isNull();
+ assertThat(config.getAllowedOriginPatterns()).containsExactly("*");
assertThat(config.getAllowedHeaders()).containsExactly("*");
assertThat(config.getAllowCredentials()).isTrue();
assertThat(CollectionUtils.isEmpty(config.getExposedHeaders())).isTrue();
@@ -330,7 +347,8 @@ class CrossOriginTests {
CorsConfiguration config = getCorsConfiguration(chain, true);
assertThat(config).isNotNull();
assertThat(config.getAllowedMethods()).containsExactly("*");
- assertThat(config.getAllowedOrigins()).containsExactly("*");
+ assertThat(config.getAllowedOrigins()).isNull();
+ assertThat(config.getAllowedOriginPatterns()).containsExactly("*");
assertThat(config.getAllowedHeaders()).containsExactly("*");
assertThat(config.getAllowCredentials()).isTrue();
assertThat(CollectionUtils.isEmpty(config.getExposedHeaders())).isTrue();
@@ -433,7 +451,7 @@ class CrossOriginTests {
public void customOriginDefinedViaPlaceholder() {
}
- @CrossOrigin(originPatterns = ".*\\.example\\.com")
+ @CrossOrigin(originPatterns = "http://*.example.com")
@RequestMapping("/customOriginPattern")
public void customOriginPatternDefinedViaValueAttribute() {
}
@@ -469,11 +487,31 @@ class CrossOriginTests {
public void bar() {
}
- @CrossOrigin(allowCredentials = "true")
+ @CrossOrigin(originPatterns = "*", allowCredentials = "true")
@RequestMapping(path = "/baz", method = RequestMethod.GET)
public void baz() {
}
+ }
+
+ @Controller
+ @CrossOrigin(allowCredentials = "true")
+ private static class CredentialsWithDefaultOriginController {
+
+ @GetMapping(path = "/no-origin")
+ public void noOrigin() {
+ }
+ }
+
+
+ @Controller
+ @CrossOrigin(allowCredentials = "true")
+ private static class CredentialsWithWildcardOriginController {
+
+ @GetMapping(path = "/no-origin")
+ @CrossOrigin(origins = "*")
+ public void wildcardOrigin() {
+ }
}
@@ -495,6 +533,8 @@ class CrossOriginTests {
@RequestMapping(path = "/foo", method = RequestMethod.GET)
public void foo() {
}
+
+
}
diff --git a/spring-webmvc/src/test/resources/org/springframework/web/servlet/config/mvc-config-cors.xml b/spring-webmvc/src/test/resources/org/springframework/web/servlet/config/mvc-config-cors.xml
index bc4e2d77fe0..83abf4075a5 100644
--- a/spring-webmvc/src/test/resources/org/springframework/web/servlet/config/mvc-config-cors.xml
+++ b/spring-webmvc/src/test/resources/org/springframework/web/servlet/config/mvc-config-cors.xml
@@ -10,6 +10,7 @@
diff --git a/src/docs/asciidoc/web/webflux-cors.adoc b/src/docs/asciidoc/web/webflux-cors.adoc
index 58cc23f17f5..601120cf42b 100644
--- a/src/docs/asciidoc/web/webflux-cors.adoc
+++ b/src/docs/asciidoc/web/webflux-cors.adoc
@@ -128,10 +128,11 @@ By default, `@CrossOrigin` allows:
* All headers.
* All HTTP methods to which the controller method is mapped.
-
-`allowedCredentials` is not enabled by default, since that establishes a trust level
+`allowCredentials` is not enabled by default, since that establishes a trust level
that exposes sensitive user-specific information (such as cookies and CSRF tokens) and
-should be used only where appropriate.
+should be used only where appropriate. When it is enabled either `allowOrigins` must be
+set to one or more specific domain (but not the special value `"*"`) or alternatively
+the `allowOriginPatterns` property may be used to match to a dynamic set of origins.
`maxAge` is set to 30 minutes.
@@ -245,7 +246,9 @@ By default global configuration enables the following:
`allowedCredentials` is not enabled by default, since that establishes a trust level
that exposes sensitive user-specific information( such as cookies and CSRF tokens) and
-should be used only where appropriate.
+should be used only where appropriate. When it is enabled either `allowOrigins` must be
+set to one or more specific domain (but not the special value `"*"`) or alternatively
+the `allowOriginPatterns` property may be used to match to a dynamic set of origins.
`maxAge` is set to 30 minutes.
diff --git a/src/docs/asciidoc/web/webmvc-cors.adoc b/src/docs/asciidoc/web/webmvc-cors.adoc
index aee826ced2b..f86e775c39c 100644
--- a/src/docs/asciidoc/web/webmvc-cors.adoc
+++ b/src/docs/asciidoc/web/webmvc-cors.adoc
@@ -59,7 +59,7 @@ class- or method-level `@CrossOrigin` annotations (other handlers can implement
The rules for combining global and local configuration are generally additive -- for example,
all global and all local origins. For those attributes where only a single value can be
-accepted (such as `allowCredentials` and `maxAge`), the local overrides the global value. See
+accepted, e.g. `allowCredentials` and `maxAge`, the local overrides the global value. See
{api-spring-framework}/web/cors/CorsConfiguration.html#combine-org.springframework.web.cors.CorsConfiguration-[`CorsConfiguration#combine(CorsConfiguration)`]
for more details.
@@ -128,9 +128,11 @@ By default, `@CrossOrigin` allows:
* All headers.
* All HTTP methods to which the controller method is mapped.
-`allowedCredentials` is not enabled by default, since that establishes a trust level
+`allowCredentials` is not enabled by default, since that establishes a trust level
that exposes sensitive user-specific information (such as cookies and CSRF tokens) and
-should only be used where appropriate.
+should only be used where appropriate. When it is enabled either `allowOrigins` must be
+set to one or more specific domain (but not the special value `"*"`) or alternatively
+the `allowOriginPatterns` property may be used to match to a dynamic set of origins.
`maxAge` is set to 30 minutes.
@@ -238,9 +240,11 @@ By default, global configuration enables the following:
* `GET`, `HEAD`, and `POST` methods.
-`allowedCredentials` is not enabled by default, since that establishes a trust level
+`allowCredentials` is not enabled by default, since that establishes a trust level
that exposes sensitive user-specific information (such as cookies and CSRF tokens) and
-should only be used where appropriate.
+should only be used where appropriate. When it is enabled either `allowOrigins` must be
+set to one or more specific domain (but not the special value `"*"`) or alternatively
+the `allowOriginPatterns` property may be used to match to a dynamic set of origins.
`maxAge` is set to 30 minutes.