CorsConfiguration now supports pattern based origins.

Closes gh-24763
This commit is contained in:
Ruslan Akhundov
2020-05-04 09:55:30 +01:00
committed by Rossen Stoyanchev
parent a1bab14140
commit 8632118e8d
9 changed files with 308 additions and 19 deletions

View File

@@ -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.
@@ -45,6 +45,7 @@ import org.springframework.web.cors.CorsConfiguration;
* @author Russell Allen
* @author Sebastien Deleuze
* @author Sam Brannen
* @author Ruslan Akhundov
* @since 4.2
*/
@Target({ElementType.TYPE, ElementType.METHOD})
@@ -93,6 +94,23 @@ public @interface CrossOrigin {
@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.
* <p>A matched origin is listed in the {@code Access-Control-Allow-Origin}
* response header of preflight actual CORS requests.
* <p>By default all origins are allowed.
* <p><strong>Note:</strong> CORS checks use values from "Forwarded"
* (<a href="https://tools.ietf.org/html/rfc7239">RFC 7239</a>),
* "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
*/
String[] originsPatterns() default {};
/**
* The list of request headers that are permitted in actual requests,
* possibly {@code "*"} to allow all headers.

View File

@@ -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.
@@ -23,6 +23,7 @@ import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import org.springframework.http.HttpMethod;
@@ -45,6 +46,7 @@ import org.springframework.util.StringUtils;
* @author Rossen Stoyanchev
* @author Juergen Hoeller
* @author Sam Brannen
* @author Ruslan Akhundov
* @since 4.2
* @see <a href="https://www.w3.org/TR/cors/">CORS spec</a>
*/
@@ -52,6 +54,8 @@ public class CorsConfiguration {
/** Wildcard representing <em>all</em> origins, methods, or headers. */
public static final String ALL = "*";
/** Wildcard representing pattern that matches <em>all</em> origins. */
public static final String ALL_PATTERN = ".*";
private static final List<HttpMethod> DEFAULT_METHODS = Collections.unmodifiableList(
Arrays.asList(HttpMethod.GET, HttpMethod.HEAD));
@@ -62,10 +66,19 @@ public class CorsConfiguration {
private static final List<String> DEFAULT_PERMIT_ALL = Collections.unmodifiableList(
Collections.singletonList(ALL));
private static final List<String> DEFAULT_PERMIT_ALL_PATTERN_STR = Collections.unmodifiableList(
Collections.singletonList(ALL_PATTERN));
private static final List<Pattern> DEFAULT_PERMIT_ALL_PATTERN = Collections.unmodifiableList(
Collections.singletonList(Pattern.compile(ALL_PATTERN)));
@Nullable
private List<String> allowedOrigins;
@Nullable
private List<Pattern> allowedOriginsPatterns;
@Nullable
private List<String> allowedMethods;
@@ -99,6 +112,7 @@ public class CorsConfiguration {
*/
public CorsConfiguration(CorsConfiguration other) {
this.allowedOrigins = other.allowedOrigins;
this.allowedOriginsPatterns = other.allowedOriginsPatterns;
this.allowedMethods = other.allowedMethods;
this.resolvedMethods = other.resolvedMethods;
this.allowedHeaders = other.allowedHeaders;
@@ -140,6 +154,54 @@ public class CorsConfiguration {
this.allowedOrigins.add(origin);
}
/**
* Set the origins patterns to allow, e.g. {@code "*.com"}.
* <p>By default this is not set.
*/
public CorsConfiguration setAllowedOriginsPatterns(@Nullable List<String> allowedOriginsPatterns) {
if (allowedOriginsPatterns == null) {
this.allowedOriginsPatterns = null;
}
else {
this.allowedOriginsPatterns = new ArrayList<>(allowedOriginsPatterns.size());
for (String pattern : allowedOriginsPatterns) {
this.allowedOriginsPatterns.add(Pattern.compile(pattern));
}
}
return this;
}
/**
* Return the configured origins patterns to allow, or {@code null} if none.
*
* @see #addAllowedOriginPattern(String)
* @see #setAllowedOriginsPatterns(List)
*/
@Nullable
public List<String> getAllowedOriginsPatterns() {
if (this.allowedOriginsPatterns == null) {
return null;
}
if (this.allowedOriginsPatterns == DEFAULT_PERMIT_ALL_PATTERN) {
return DEFAULT_PERMIT_ALL_PATTERN_STR;
}
return this.allowedOriginsPatterns.stream().map(Pattern::toString).collect(Collectors.toList());
}
/**
* Add an origin pattern to allow.
*/
public void addAllowedOriginPattern(String originPattern) {
if (this.allowedOriginsPatterns == null) {
this.allowedOriginsPatterns = new ArrayList<>(4);
}
else if (this.allowedOriginsPatterns == DEFAULT_PERMIT_ALL_PATTERN) {
setAllowedOriginsPatterns(DEFAULT_PERMIT_ALL_PATTERN_STR);
}
this.allowedOriginsPatterns.add(Pattern.compile(originPattern));
}
/**
* Set the HTTP methods to allow, e.g. {@code "GET"}, {@code "POST"},
* {@code "PUT"}, etc.
@@ -351,7 +413,7 @@ public class CorsConfiguration {
* </ul>
*/
public CorsConfiguration applyPermitDefaultValues() {
if (this.allowedOrigins == null) {
if (this.allowedOrigins == null && this.allowedOriginsPatterns == null) {
this.allowedOrigins = DEFAULT_PERMIT_ALL;
}
if (this.allowedMethods == null) {
@@ -392,7 +454,14 @@ public class CorsConfiguration {
return this;
}
CorsConfiguration config = new CorsConfiguration(this);
config.setAllowedOrigins(combine(getAllowedOrigins(), other.getAllowedOrigins()));
List<String> combinedOrigins = combine(getAllowedOrigins(), other.getAllowedOrigins());
List<String> combinedOriginsPatterns = combine(getAllowedOriginsPatterns(), other.getAllowedOriginsPatterns());
if (combinedOrigins == DEFAULT_PERMIT_ALL && combinedOriginsPatterns != DEFAULT_PERMIT_ALL_PATTERN_STR
&& !CollectionUtils.isEmpty(combinedOriginsPatterns)) {
combinedOrigins = null;
}
config.setAllowedOrigins(combinedOrigins);
config.setAllowedOriginsPatterns(combinedOriginsPatterns);
config.setAllowedMethods(combine(getAllowedMethods(), other.getAllowedMethods()));
config.setAllowedHeaders(combine(getAllowedHeaders(), other.getAllowedHeaders()));
config.setExposedHeaders(combine(getExposedHeaders(), other.getExposedHeaders()));
@@ -414,15 +483,20 @@ public class CorsConfiguration {
if (source == null) {
return other;
}
if (source == DEFAULT_PERMIT_ALL || source == DEFAULT_PERMIT_METHODS) {
if (source == DEFAULT_PERMIT_ALL || source == DEFAULT_PERMIT_METHODS
|| source == DEFAULT_PERMIT_ALL_PATTERN_STR) {
return other;
}
if (other == DEFAULT_PERMIT_ALL || other == DEFAULT_PERMIT_METHODS) {
if (other == DEFAULT_PERMIT_ALL || other == DEFAULT_PERMIT_METHODS
|| other == DEFAULT_PERMIT_ALL_PATTERN_STR) {
return source;
}
if (source.contains(ALL) || other.contains(ALL)) {
return new ArrayList<>(Collections.singletonList(ALL));
}
if ( source.contains(ALL_PATTERN) || other.contains(ALL_PATTERN)) {
return new ArrayList<>(Collections.singletonList(ALL_PATTERN));
}
Set<String> combined = new LinkedHashSet<>(source);
combined.addAll(other);
return new ArrayList<>(combined);
@@ -439,21 +513,35 @@ public class CorsConfiguration {
if (!StringUtils.hasText(requestOrigin)) {
return null;
}
if (ObjectUtils.isEmpty(this.allowedOrigins)) {
return null;
}
if (this.allowedOrigins.contains(ALL)) {
if (this.allowCredentials != Boolean.TRUE) {
return ALL;
if (!ObjectUtils.isEmpty(this.allowedOrigins)) {
if (this.allowedOrigins.contains(ALL)) {
if (this.allowCredentials != Boolean.TRUE) {
return ALL;
}
else {
return requestOrigin;
}
}
else {
return requestOrigin;
for (String allowedOrigin : this.allowedOrigins) {
if (requestOrigin.equalsIgnoreCase(allowedOrigin)) {
return requestOrigin;
}
}
}
for (String allowedOrigin : this.allowedOrigins) {
if (requestOrigin.equalsIgnoreCase(allowedOrigin)) {
return requestOrigin;
if (!ObjectUtils.isEmpty(this.allowedOriginsPatterns)) {
for (Pattern allowedOriginsPattern : this.allowedOriginsPatterns) {
if (allowedOriginsPattern.pattern().equals(ALL_PATTERN)) {
if (this.allowCredentials != Boolean.TRUE) {
return ALL;
}
else {
return requestOrigin;
}
}
else if (allowedOriginsPattern.matcher(requestOrigin).matches()) {
return requestOrigin;
}
}
}