Updates to CORS patterns contribution
Closes gh-25016
This commit is contained in:
@@ -21,6 +21,7 @@ import java.lang.annotation.ElementType;
|
|||||||
import java.lang.annotation.Retention;
|
import java.lang.annotation.Retention;
|
||||||
import java.lang.annotation.RetentionPolicy;
|
import java.lang.annotation.RetentionPolicy;
|
||||||
import java.lang.annotation.Target;
|
import java.lang.annotation.Target;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
import org.springframework.core.annotation.AliasFor;
|
import org.springframework.core.annotation.AliasFor;
|
||||||
import org.springframework.web.cors.CorsConfiguration;
|
import org.springframework.web.cors.CorsConfiguration;
|
||||||
@@ -77,37 +78,20 @@ public @interface CrossOrigin {
|
|||||||
String[] value() default {};
|
String[] value() default {};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The list of allowed origins that be specific origins, e.g.
|
* A list of origins for which cross-origin requests are allowed. Please,
|
||||||
* {@code "https://domain1.com"}, or {@code "*"} for all origins.
|
* see {@link CorsConfiguration#setAllowedOrigins(List)} for details.
|
||||||
* <p>A matched origin is listed in the {@code Access-Control-Allow-Origin}
|
* <p>By default all origins are allowed unless {@code originPatterns} is
|
||||||
* response header of preflight actual CORS requests.
|
* also set in which case {@code originPatterns} is used instead.
|
||||||
* <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
|
|
||||||
*/
|
*/
|
||||||
@AliasFor("value")
|
@AliasFor("value")
|
||||||
String[] origins() default {};
|
String[] origins() default {};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The list of allowed origins patterns that be specific origins, e.g.
|
* Alternative to {@link #origins()} that supports origins declared via
|
||||||
* {@code ".*\.domain1\.com"}, or {@code ".*"} for matching all origins.
|
* wildcard patterns. Please, see
|
||||||
* <p>A matched origin is listed in the {@code Access-Control-Allow-Origin}
|
* @link CorsConfiguration#setAllowedOriginPatterns(List)} for details.
|
||||||
* response header of preflight actual CORS requests.
|
* <p>By default this is not set.
|
||||||
* <p>By default all origins are allowed.
|
* @since 5.3
|
||||||
* <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[] originPatterns() default {};
|
String[] originPatterns() default {};
|
||||||
|
|
||||||
|
|||||||
@@ -54,8 +54,17 @@ public class CorsConfiguration {
|
|||||||
|
|
||||||
/** Wildcard representing <em>all</em> origins, methods, or headers. */
|
/** Wildcard representing <em>all</em> origins, methods, or headers. */
|
||||||
public static final String ALL = "*";
|
public static final String ALL = "*";
|
||||||
/** Wildcard representing pattern that matches <em>all</em> origins. */
|
|
||||||
public static final String ALL_PATTERN = ".*";
|
private static final List<String> ALL_LIST = Collections.unmodifiableList(
|
||||||
|
Collections.singletonList(ALL));
|
||||||
|
|
||||||
|
private static final OriginPattern ALL_PATTERN = new OriginPattern("*");
|
||||||
|
|
||||||
|
private static final List<OriginPattern> ALL_PATTERN_LIST = Collections.unmodifiableList(
|
||||||
|
Collections.singletonList(ALL_PATTERN));
|
||||||
|
|
||||||
|
private static final List<String> DEFAULT_PERMIT_ALL = Collections.unmodifiableList(
|
||||||
|
Collections.singletonList(ALL));
|
||||||
|
|
||||||
private static final List<HttpMethod> DEFAULT_METHODS = Collections.unmodifiableList(
|
private static final List<HttpMethod> DEFAULT_METHODS = Collections.unmodifiableList(
|
||||||
Arrays.asList(HttpMethod.GET, HttpMethod.HEAD));
|
Arrays.asList(HttpMethod.GET, HttpMethod.HEAD));
|
||||||
@@ -63,21 +72,12 @@ public class CorsConfiguration {
|
|||||||
private static final List<String> DEFAULT_PERMIT_METHODS = Collections.unmodifiableList(
|
private static final List<String> DEFAULT_PERMIT_METHODS = Collections.unmodifiableList(
|
||||||
Arrays.asList(HttpMethod.GET.name(), HttpMethod.HEAD.name(), HttpMethod.POST.name()));
|
Arrays.asList(HttpMethod.GET.name(), HttpMethod.HEAD.name(), HttpMethod.POST.name()));
|
||||||
|
|
||||||
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
|
@Nullable
|
||||||
private List<String> allowedOrigins;
|
private List<String> allowedOrigins;
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
private List<Pattern> allowedOriginPatterns;
|
private List<OriginPattern> allowedOriginPatterns;
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
private List<String> allowedMethods;
|
private List<String> allowedMethods;
|
||||||
@@ -123,9 +123,19 @@ public class CorsConfiguration {
|
|||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set the origins to allow, e.g. {@code "https://domain1.com"}.
|
* A list of origins for which cross-origin requests are allowed. Values may
|
||||||
* <p>The special value {@code "*"} allows all domains.
|
* be a specific domain, e.g. {@code "https://domain1.com"}, or the CORS
|
||||||
* <p>By default this is not set.
|
* defined special value {@code "*"} for all origins.
|
||||||
|
* <p>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.
|
||||||
|
* <p>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<String> allowedOrigins) {
|
public void setAllowedOrigins(@Nullable List<String> allowedOrigins) {
|
||||||
this.allowedOrigins = (allowedOrigins != null ? new ArrayList<>(allowedOrigins) : null);
|
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.
|
* Return the configured origins to allow, or {@code null} if none.
|
||||||
* @see #addAllowedOrigin(String)
|
|
||||||
* @see #setAllowedOrigins(List)
|
|
||||||
*/
|
*/
|
||||||
@Nullable
|
@Nullable
|
||||||
public List<String> getAllowedOrigins() {
|
public List<String> 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) {
|
public void addAllowedOrigin(String origin) {
|
||||||
if (this.allowedOrigins == null) {
|
if (this.allowedOrigins == null) {
|
||||||
this.allowedOrigins = new ArrayList<>(4);
|
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);
|
setAllowedOrigins(DEFAULT_PERMIT_ALL);
|
||||||
}
|
}
|
||||||
this.allowedOrigins.add(origin);
|
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}.
|
||||||
* <p>By default this is not set.
|
* <p>By default this is not set.
|
||||||
|
* @since 5.3
|
||||||
*/
|
*/
|
||||||
public CorsConfiguration setAllowedOriginPatterns(@Nullable List<String> allowedOriginPatterns) {
|
public CorsConfiguration setAllowedOriginPatterns(@Nullable List<String> allowedOriginPatterns) {
|
||||||
if (allowedOriginPatterns == null) {
|
if (allowedOriginPatterns == null) {
|
||||||
@@ -164,42 +180,39 @@ public class CorsConfiguration {
|
|||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
this.allowedOriginPatterns = new ArrayList<>(allowedOriginPatterns.size());
|
this.allowedOriginPatterns = new ArrayList<>(allowedOriginPatterns.size());
|
||||||
for (String pattern : allowedOriginPatterns) {
|
for (String patternValue : allowedOriginPatterns) {
|
||||||
this.allowedOriginPatterns.add(Pattern.compile(pattern));
|
addAllowedOriginPattern(patternValue);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return the configured origins patterns to allow, or {@code null} if none.
|
* Return the configured origins patterns to allow, or {@code null} if none.
|
||||||
*
|
* @since 5.3
|
||||||
* @see #addAllowedOriginPattern(String)
|
|
||||||
* @see #setAllowedOriginPatterns(List)
|
|
||||||
*/
|
*/
|
||||||
@Nullable
|
@Nullable
|
||||||
public List<String> getAllowedOriginPatterns() {
|
public List<String> getAllowedOriginPatterns() {
|
||||||
if (this.allowedOriginPatterns == null) {
|
if (this.allowedOriginPatterns == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
if (this.allowedOriginPatterns == DEFAULT_PERMIT_ALL_PATTERN) {
|
return this.allowedOriginPatterns.stream()
|
||||||
return DEFAULT_PERMIT_ALL_PATTERN_STR;
|
.map(OriginPattern::getDeclaredPattern)
|
||||||
}
|
.collect(Collectors.toList());
|
||||||
return this.allowedOriginPatterns.stream().map(Pattern::toString).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) {
|
public void addAllowedOriginPattern(String originPattern) {
|
||||||
if (this.allowedOriginPatterns == null) {
|
if (this.allowedOriginPatterns == null) {
|
||||||
this.allowedOriginPatterns = new ArrayList<>(4);
|
this.allowedOriginPatterns = new ArrayList<>(4);
|
||||||
}
|
}
|
||||||
else if (this.allowedOriginPatterns == DEFAULT_PERMIT_ALL_PATTERN) {
|
this.allowedOriginPatterns.add(new OriginPattern(originPattern));
|
||||||
setAllowedOriginPatterns(DEFAULT_PERMIT_ALL_PATTERN_STR);
|
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
|
* By default {@code CorsConfiguration} does not permit any cross-origin
|
||||||
* cross-origin requests and must be configured explicitly to indicate what
|
* requests and must be configured explicitly. Use this method to switch to
|
||||||
* should be allowed.
|
* defaults that permit all cross-origin requests for GET, HEAD, and POST,
|
||||||
* <p>Use this method to flip the initialization model to start with open
|
* but not overriding any values that have already been set.
|
||||||
* defaults that permit all cross-origin requests for GET, HEAD, and POST
|
* <p>The following defaults are applied for values that are not set:
|
||||||
* requests. Note however that this method will not override any existing
|
|
||||||
* values already set.
|
|
||||||
* <p>The following defaults are applied if not already set:
|
|
||||||
* <ul>
|
* <ul>
|
||||||
* <li>Allow all origins.</li>
|
* <li>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.</li>
|
||||||
* <li>Allow "simple" methods {@code GET}, {@code HEAD} and {@code POST}.</li>
|
* <li>Allow "simple" methods {@code GET}, {@code HEAD} and {@code POST}.</li>
|
||||||
* <li>Allow all headers.</li>
|
* <li>Allow all headers.</li>
|
||||||
* <li>Set max age to 1800 seconds (30 minutes).</li>
|
* <li>Set max age to 1800 seconds (30 minutes).</li>
|
||||||
@@ -430,6 +442,26 @@ public class CorsConfiguration {
|
|||||||
return this;
|
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
|
* Combine the non-null properties of the supplied
|
||||||
* {@code CorsConfiguration} with this one.
|
* {@code CorsConfiguration} with this one.
|
||||||
@@ -439,12 +471,11 @@ public class CorsConfiguration {
|
|||||||
* <p>Combining lists like {@code allowedOrigins}, {@code allowedMethods},
|
* <p>Combining lists like {@code allowedOrigins}, {@code allowedMethods},
|
||||||
* {@code allowedHeaders} or {@code exposedHeaders} is done in an additive
|
* {@code allowedHeaders} or {@code exposedHeaders} is done in an additive
|
||||||
* way. For example, combining {@code ["GET", "POST"]} with
|
* way. For example, combining {@code ["GET", "POST"]} with
|
||||||
* {@code ["PATCH"]} results in {@code ["GET", "POST", "PATCH"]}, but keep
|
* {@code ["PATCH"]} results in {@code ["GET", "POST", "PATCH"]}. However,
|
||||||
* in mind that combining {@code ["GET", "POST"]} with {@code ["*"]}
|
* combining {@code ["GET", "POST"]} with {@code ["*"]} results in
|
||||||
* results in {@code ["*"]}.
|
* {@code ["*"]}. Note also that default permit values set by
|
||||||
* <p>Notice that default permit values set by
|
|
||||||
* {@link CorsConfiguration#applyPermitDefaultValues()} are overridden by
|
* {@link CorsConfiguration#applyPermitDefaultValues()} are overridden by
|
||||||
* any value explicitly defined.
|
* any explicitly defined values.
|
||||||
* @return the combined {@code CorsConfiguration}, or {@code this}
|
* @return the combined {@code CorsConfiguration}, or {@code this}
|
||||||
* configuration if the supplied configuration is {@code null}
|
* configuration if the supplied configuration is {@code null}
|
||||||
*/
|
*/
|
||||||
@@ -453,15 +484,12 @@ public class CorsConfiguration {
|
|||||||
if (other == null) {
|
if (other == null) {
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
// Bypass setAllowedOrigins to avoid re-compiling patterns
|
||||||
CorsConfiguration config = new CorsConfiguration(this);
|
CorsConfiguration config = new CorsConfiguration(this);
|
||||||
List<String> combinedOrigins = combine(getAllowedOrigins(), other.getAllowedOrigins());
|
List<String> origins = combine(getAllowedOrigins(), other.getAllowedOrigins());
|
||||||
List<String> combinedOriginPatterns = combine(getAllowedOriginPatterns(), other.getAllowedOriginPatterns());
|
List<OriginPattern> patterns = combinePatterns(this.allowedOriginPatterns, other.allowedOriginPatterns);
|
||||||
if (combinedOrigins == DEFAULT_PERMIT_ALL && combinedOriginPatterns != DEFAULT_PERMIT_ALL_PATTERN_STR
|
config.allowedOrigins = (origins == DEFAULT_PERMIT_ALL && !CollectionUtils.isEmpty(patterns) ? null : origins);
|
||||||
&& !CollectionUtils.isEmpty(combinedOriginPatterns)) {
|
config.allowedOriginPatterns = patterns;
|
||||||
combinedOrigins = null;
|
|
||||||
}
|
|
||||||
config.setAllowedOrigins(combinedOrigins);
|
|
||||||
config.setAllowedOriginPatterns(combinedOriginPatterns);
|
|
||||||
config.setAllowedMethods(combine(getAllowedMethods(), other.getAllowedMethods()));
|
config.setAllowedMethods(combine(getAllowedMethods(), other.getAllowedMethods()));
|
||||||
config.setAllowedHeaders(combine(getAllowedHeaders(), other.getAllowedHeaders()));
|
config.setAllowedHeaders(combine(getAllowedHeaders(), other.getAllowedHeaders()));
|
||||||
config.setExposedHeaders(combine(getExposedHeaders(), other.getExposedHeaders()));
|
config.setExposedHeaders(combine(getExposedHeaders(), other.getExposedHeaders()));
|
||||||
@@ -483,25 +511,40 @@ public class CorsConfiguration {
|
|||||||
if (source == null) {
|
if (source == null) {
|
||||||
return other;
|
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;
|
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;
|
return source;
|
||||||
}
|
}
|
||||||
if (source.contains(ALL) || other.contains(ALL)) {
|
if (source.contains(ALL) || other.contains(ALL)) {
|
||||||
return new ArrayList<>(Collections.singletonList(ALL));
|
return ALL_LIST;
|
||||||
}
|
}
|
||||||
if ( source.contains(ALL_PATTERN) || other.contains(ALL_PATTERN)) {
|
Set<String> combined = new LinkedHashSet<>(source.size() + other.size());
|
||||||
return new ArrayList<>(Collections.singletonList(ALL_PATTERN));
|
combined.addAll(source);
|
||||||
}
|
|
||||||
Set<String> combined = new LinkedHashSet<>(source);
|
|
||||||
combined.addAll(other);
|
combined.addAll(other);
|
||||||
return new ArrayList<>(combined);
|
return new ArrayList<>(combined);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private List<OriginPattern> combinePatterns(
|
||||||
|
@Nullable List<OriginPattern> source, @Nullable List<OriginPattern> other) {
|
||||||
|
|
||||||
|
if (other == null) {
|
||||||
|
return (source != null ? source : Collections.emptyList());
|
||||||
|
}
|
||||||
|
if (source == null) {
|
||||||
|
return other;
|
||||||
|
}
|
||||||
|
if (source.contains(ALL_PATTERN) || other.contains(ALL_PATTERN)) {
|
||||||
|
return ALL_PATTERN_LIST;
|
||||||
|
}
|
||||||
|
Set<OriginPattern> 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.
|
* Check the origin of the request against the configured allowed origins.
|
||||||
* @param requestOrigin the origin to check
|
* @param requestOrigin the origin to check
|
||||||
@@ -513,15 +556,10 @@ public class CorsConfiguration {
|
|||||||
if (!StringUtils.hasText(requestOrigin)) {
|
if (!StringUtils.hasText(requestOrigin)) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!ObjectUtils.isEmpty(this.allowedOrigins)) {
|
if (!ObjectUtils.isEmpty(this.allowedOrigins)) {
|
||||||
if (this.allowedOrigins.contains(ALL)) {
|
if (this.allowedOrigins.contains(ALL)) {
|
||||||
if (this.allowCredentials != Boolean.TRUE) {
|
validateAllowCredentials();
|
||||||
return ALL;
|
return ALL;
|
||||||
}
|
|
||||||
else {
|
|
||||||
return requestOrigin;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
for (String allowedOrigin : this.allowedOrigins) {
|
for (String allowedOrigin : this.allowedOrigins) {
|
||||||
if (requestOrigin.equalsIgnoreCase(allowedOrigin)) {
|
if (requestOrigin.equalsIgnoreCase(allowedOrigin)) {
|
||||||
@@ -530,21 +568,12 @@ public class CorsConfiguration {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!ObjectUtils.isEmpty(this.allowedOriginPatterns)) {
|
if (!ObjectUtils.isEmpty(this.allowedOriginPatterns)) {
|
||||||
for (Pattern allowedOriginsPattern : this.allowedOriginPatterns) {
|
for (OriginPattern p : this.allowedOriginPatterns) {
|
||||||
if (allowedOriginsPattern.pattern().equals(ALL_PATTERN)) {
|
if (p.getDeclaredPattern().equals(ALL) || p.getPattern().matcher(requestOrigin).matches()) {
|
||||||
if (this.allowCredentials != Boolean.TRUE) {
|
|
||||||
return ALL;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
return requestOrigin;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (allowedOriginsPattern.matcher(requestOrigin).matches()) {
|
|
||||||
return requestOrigin;
|
return requestOrigin;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -608,4 +637,57 @@ public class CorsConfiguration {
|
|||||||
return (result.isEmpty() ? null : result);
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -40,6 +40,8 @@ public class CorsConfigurationTests {
|
|||||||
CorsConfiguration config = new CorsConfiguration();
|
CorsConfiguration config = new CorsConfiguration();
|
||||||
config.setAllowedOrigins(null);
|
config.setAllowedOrigins(null);
|
||||||
assertThat(config.getAllowedOrigins()).isNull();
|
assertThat(config.getAllowedOrigins()).isNull();
|
||||||
|
config.setAllowedOriginPatterns(null);
|
||||||
|
assertThat(config.getAllowedOriginPatterns()).isNull();
|
||||||
config.setAllowedHeaders(null);
|
config.setAllowedHeaders(null);
|
||||||
assertThat(config.getAllowedHeaders()).isNull();
|
assertThat(config.getAllowedHeaders()).isNull();
|
||||||
config.setAllowedMethods(null);
|
config.setAllowedMethods(null);
|
||||||
@@ -50,42 +52,39 @@ public class CorsConfigurationTests {
|
|||||||
assertThat(config.getAllowCredentials()).isNull();
|
assertThat(config.getAllowCredentials()).isNull();
|
||||||
config.setMaxAge((Long) null);
|
config.setMaxAge((Long) null);
|
||||||
assertThat(config.getMaxAge()).isNull();
|
assertThat(config.getMaxAge()).isNull();
|
||||||
config.setAllowedOriginPatterns(null);
|
|
||||||
assertThat(config.getAllowedOriginPatterns()).isNull();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void setValues() {
|
public void setValues() {
|
||||||
CorsConfiguration config = new CorsConfiguration();
|
CorsConfiguration config = new CorsConfiguration();
|
||||||
config.addAllowedOrigin("*");
|
config.addAllowedOrigin("*");
|
||||||
assertThat(config.getAllowedOrigins()).containsExactly("*");
|
config.addAllowedOriginPattern("http://*.example.com");
|
||||||
config.addAllowedHeader("*");
|
config.addAllowedHeader("*");
|
||||||
assertThat(config.getAllowedHeaders()).containsExactly("*");
|
|
||||||
config.addAllowedMethod("*");
|
config.addAllowedMethod("*");
|
||||||
assertThat(config.getAllowedMethods()).containsExactly("*");
|
|
||||||
config.addExposedHeader("header1");
|
config.addExposedHeader("header1");
|
||||||
config.addExposedHeader("header2");
|
config.addExposedHeader("header2");
|
||||||
assertThat(config.getExposedHeaders()).containsExactly("header1", "header2");
|
|
||||||
config.setAllowCredentials(true);
|
config.setAllowCredentials(true);
|
||||||
assertThat(config.getAllowCredentials()).isTrue();
|
|
||||||
config.setMaxAge(123L);
|
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));
|
assertThat(config.getMaxAge()).isEqualTo(new Long(123));
|
||||||
config.addAllowedOriginPattern(".*\\.example\\.com");
|
|
||||||
assertThat(config.getAllowedOriginPatterns()).containsExactly(".*\\.example\\.com");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void asteriskWildCardOnAddExposedHeader() {
|
public void asteriskWildCardOnAddExposedHeader() {
|
||||||
CorsConfiguration config = new CorsConfiguration();
|
assertThatIllegalArgumentException()
|
||||||
assertThatIllegalArgumentException().isThrownBy(() ->
|
.isThrownBy(() -> new CorsConfiguration().addExposedHeader("*"));
|
||||||
config.addExposedHeader("*"));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void asteriskWildCardOnSetExposedHeaders() {
|
public void asteriskWildCardOnSetExposedHeaders() {
|
||||||
CorsConfiguration config = new CorsConfiguration();
|
|
||||||
assertThatIllegalArgumentException()
|
assertThatIllegalArgumentException()
|
||||||
.isThrownBy(() -> config.setExposedHeaders(Collections.singletonList("*")));
|
.isThrownBy(() -> new CorsConfiguration().setExposedHeaders(Collections.singletonList("*")));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -94,28 +93,31 @@ public class CorsConfigurationTests {
|
|||||||
config.setAllowedOrigins(Collections.singletonList("*"));
|
config.setAllowedOrigins(Collections.singletonList("*"));
|
||||||
config.combine(null);
|
config.combine(null);
|
||||||
assertThat(config.getAllowedOrigins()).containsExactly("*");
|
assertThat(config.getAllowedOrigins()).containsExactly("*");
|
||||||
|
assertThat(config.getAllowedOriginPatterns()).isNull();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void combineWithNullProperties() {
|
public void combineWithNullProperties() {
|
||||||
CorsConfiguration config = new CorsConfiguration();
|
CorsConfiguration config = new CorsConfiguration();
|
||||||
config.addAllowedOrigin("*");
|
config.addAllowedOrigin("*");
|
||||||
|
config.setAllowedOriginPatterns(Collections.singletonList("http://*.example.com"));
|
||||||
config.addAllowedHeader("header1");
|
config.addAllowedHeader("header1");
|
||||||
config.addExposedHeader("header3");
|
config.addExposedHeader("header3");
|
||||||
config.addAllowedMethod(HttpMethod.GET.name());
|
config.addAllowedMethod(HttpMethod.GET.name());
|
||||||
config.setMaxAge(123L);
|
config.setMaxAge(123L);
|
||||||
config.setAllowCredentials(true);
|
config.setAllowCredentials(true);
|
||||||
config.setAllowedOriginPatterns(Collections.singletonList(".*\\.example\\.com"));
|
|
||||||
CorsConfiguration other = new CorsConfiguration();
|
CorsConfiguration other = new CorsConfiguration();
|
||||||
config = config.combine(other);
|
config = config.combine(other);
|
||||||
|
|
||||||
assertThat(config).isNotNull();
|
assertThat(config).isNotNull();
|
||||||
assertThat(config.getAllowedOrigins()).containsExactly("*");
|
assertThat(config.getAllowedOrigins()).containsExactly("*");
|
||||||
|
assertThat(config.getAllowedOriginPatterns()).containsExactly("http://*.example.com");
|
||||||
assertThat(config.getAllowedHeaders()).containsExactly("header1");
|
assertThat(config.getAllowedHeaders()).containsExactly("header1");
|
||||||
assertThat(config.getExposedHeaders()).containsExactly("header3");
|
assertThat(config.getExposedHeaders()).containsExactly("header3");
|
||||||
assertThat(config.getAllowedMethods()).containsExactly(HttpMethod.GET.name());
|
assertThat(config.getAllowedMethods()).containsExactly(HttpMethod.GET.name());
|
||||||
assertThat(config.getMaxAge()).isEqualTo(new Long(123));
|
assertThat(config.getMaxAge()).isEqualTo(new Long(123));
|
||||||
assertThat(config.getAllowCredentials()).isTrue();
|
assertThat(config.getAllowCredentials()).isTrue();
|
||||||
assertThat(config.getAllowedOriginPatterns()).containsExactly(".*\\.example\\.com");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test // SPR-15772
|
@Test // SPR-15772
|
||||||
@@ -157,35 +159,36 @@ public class CorsConfigurationTests {
|
|||||||
public void combinePatternWithDefaultPermitValues() {
|
public void combinePatternWithDefaultPermitValues() {
|
||||||
CorsConfiguration config = new CorsConfiguration().applyPermitDefaultValues();
|
CorsConfiguration config = new CorsConfiguration().applyPermitDefaultValues();
|
||||||
CorsConfiguration other = new CorsConfiguration();
|
CorsConfiguration other = new CorsConfiguration();
|
||||||
other.addAllowedOriginPattern(".*\\.com");
|
other.addAllowedOriginPattern("http://*.com");
|
||||||
|
|
||||||
CorsConfiguration combinedConfig = other.combine(config);
|
CorsConfiguration combinedConfig = other.combine(config);
|
||||||
assertThat(combinedConfig).isNotNull();
|
assertThat(combinedConfig).isNotNull();
|
||||||
assertThat(combinedConfig.getAllowedOrigins()).isNull();
|
assertThat(combinedConfig.getAllowedOrigins()).isNull();
|
||||||
assertThat(combinedConfig.getAllowedOriginPatterns()).containsExactly(".*\\.com");
|
assertThat(combinedConfig.getAllowedOriginPatterns()).containsExactly("http://*.com");
|
||||||
|
|
||||||
combinedConfig = config.combine(other);
|
combinedConfig = config.combine(other);
|
||||||
assertThat(combinedConfig).isNotNull();
|
assertThat(combinedConfig).isNotNull();
|
||||||
assertThat(combinedConfig.getAllowedOrigins()).isNull();
|
assertThat(combinedConfig.getAllowedOrigins()).isNull();
|
||||||
assertThat(combinedConfig.getAllowedOriginPatterns()).containsExactly(".*\\.com");
|
assertThat(combinedConfig.getAllowedOriginPatterns()).containsExactly("http://*.com");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void combinePatternWithDefaultPermitValuesAndCustomOrigin() {
|
public void combinePatternWithDefaultPermitValuesAndCustomOrigin() {
|
||||||
CorsConfiguration config = new CorsConfiguration().applyPermitDefaultValues();
|
CorsConfiguration config = new CorsConfiguration().applyPermitDefaultValues();
|
||||||
config.setAllowedOrigins(Collections.singletonList("https://domain.com"));
|
config.setAllowedOrigins(Collections.singletonList("https://domain.com"));
|
||||||
|
|
||||||
CorsConfiguration other = new CorsConfiguration();
|
CorsConfiguration other = new CorsConfiguration();
|
||||||
other.addAllowedOriginPattern(".*\\.com");
|
other.addAllowedOriginPattern("http://*.com");
|
||||||
|
|
||||||
CorsConfiguration combinedConfig = other.combine(config);
|
CorsConfiguration combinedConfig = other.combine(config);
|
||||||
assertThat(combinedConfig).isNotNull();
|
assertThat(combinedConfig).isNotNull();
|
||||||
assertThat(combinedConfig.getAllowedOrigins()).containsExactly("https://domain.com");
|
assertThat(combinedConfig.getAllowedOrigins()).containsExactly("https://domain.com");
|
||||||
assertThat(combinedConfig.getAllowedOriginPatterns()).containsExactly(".*\\.com");
|
assertThat(combinedConfig.getAllowedOriginPatterns()).containsExactly("http://*.com");
|
||||||
|
|
||||||
combinedConfig = config.combine(other);
|
combinedConfig = config.combine(other);
|
||||||
assertThat(combinedConfig).isNotNull();
|
assertThat(combinedConfig).isNotNull();
|
||||||
assertThat(combinedConfig.getAllowedOrigins()).containsExactly("https://domain.com");
|
assertThat(combinedConfig.getAllowedOrigins()).containsExactly("https://domain.com");
|
||||||
assertThat(combinedConfig.getAllowedOriginPatterns()).containsExactly(".*\\.com");
|
assertThat(combinedConfig.getAllowedOriginPatterns()).containsExactly("http://*.com");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -194,25 +197,28 @@ public class CorsConfigurationTests {
|
|||||||
config.addAllowedOrigin("*");
|
config.addAllowedOrigin("*");
|
||||||
config.addAllowedHeader("*");
|
config.addAllowedHeader("*");
|
||||||
config.addAllowedMethod("*");
|
config.addAllowedMethod("*");
|
||||||
config.addAllowedOriginPattern(".*");
|
config.addAllowedOriginPattern("*");
|
||||||
|
|
||||||
CorsConfiguration other = new CorsConfiguration();
|
CorsConfiguration other = new CorsConfiguration();
|
||||||
other.addAllowedOrigin("https://domain.com");
|
other.addAllowedOrigin("https://domain.com");
|
||||||
|
other.addAllowedOriginPattern("http://*.company.com");
|
||||||
other.addAllowedHeader("header1");
|
other.addAllowedHeader("header1");
|
||||||
other.addExposedHeader("header2");
|
other.addExposedHeader("header2");
|
||||||
other.addAllowedOriginPattern(".*\\.company\\.com");
|
|
||||||
other.addAllowedMethod(HttpMethod.PUT.name());
|
other.addAllowedMethod(HttpMethod.PUT.name());
|
||||||
|
|
||||||
CorsConfiguration combinedConfig = config.combine(other);
|
CorsConfiguration combinedConfig = config.combine(other);
|
||||||
assertThat(combinedConfig).isNotNull();
|
assertThat(combinedConfig).isNotNull();
|
||||||
assertThat(combinedConfig.getAllowedOrigins()).containsExactly("*");
|
assertThat(combinedConfig.getAllowedOrigins()).containsExactly("*");
|
||||||
|
assertThat(combinedConfig.getAllowedOriginPatterns()).containsExactly("*");
|
||||||
assertThat(combinedConfig.getAllowedHeaders()).containsExactly("*");
|
assertThat(combinedConfig.getAllowedHeaders()).containsExactly("*");
|
||||||
assertThat(combinedConfig.getAllowedMethods()).containsExactly("*");
|
assertThat(combinedConfig.getAllowedMethods()).containsExactly("*");
|
||||||
assertThat(combinedConfig.getAllowedOriginPatterns()).containsExactly(".*");
|
|
||||||
combinedConfig = other.combine(config);
|
combinedConfig = other.combine(config);
|
||||||
assertThat(combinedConfig).isNotNull();
|
assertThat(combinedConfig).isNotNull();
|
||||||
assertThat(combinedConfig.getAllowedOrigins()).containsExactly("*");
|
assertThat(combinedConfig.getAllowedOrigins()).containsExactly("*");
|
||||||
|
assertThat(combinedConfig.getAllowedOriginPatterns()).containsExactly("*");
|
||||||
assertThat(combinedConfig.getAllowedHeaders()).containsExactly("*");
|
assertThat(combinedConfig.getAllowedHeaders()).containsExactly("*");
|
||||||
assertThat(combinedConfig.getAllowedMethods()).containsExactly("*");
|
assertThat(combinedConfig.getAllowedMethods()).containsExactly("*");
|
||||||
assertThat(combinedConfig.getAllowedOriginPatterns()).containsExactly(".*");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test // SPR-14792
|
@Test // SPR-14792
|
||||||
@@ -226,41 +232,45 @@ public class CorsConfigurationTests {
|
|||||||
config.addExposedHeader("header4");
|
config.addExposedHeader("header4");
|
||||||
config.addAllowedMethod(HttpMethod.GET.name());
|
config.addAllowedMethod(HttpMethod.GET.name());
|
||||||
config.addAllowedMethod(HttpMethod.PUT.name());
|
config.addAllowedMethod(HttpMethod.PUT.name());
|
||||||
config.addAllowedOriginPattern(".*\\.domain1\\.com");
|
config.addAllowedOriginPattern("http://*.domain1.com");
|
||||||
config.addAllowedOriginPattern(".*\\.domain2\\.com");
|
config.addAllowedOriginPattern("http://*.domain2.com");
|
||||||
|
|
||||||
CorsConfiguration other = new CorsConfiguration();
|
CorsConfiguration other = new CorsConfiguration();
|
||||||
other.addAllowedOrigin("https://domain1.com");
|
other.addAllowedOrigin("https://domain1.com");
|
||||||
|
other.addAllowedOriginPattern("http://*.domain1.com");
|
||||||
other.addAllowedHeader("header1");
|
other.addAllowedHeader("header1");
|
||||||
other.addExposedHeader("header3");
|
other.addExposedHeader("header3");
|
||||||
other.addAllowedMethod(HttpMethod.GET.name());
|
other.addAllowedMethod(HttpMethod.GET.name());
|
||||||
other.addAllowedOriginPattern(".*\\.domain1\\.com");
|
|
||||||
CorsConfiguration combinedConfig = config.combine(other);
|
CorsConfiguration combinedConfig = config.combine(other);
|
||||||
assertThat(combinedConfig).isNotNull();
|
assertThat(combinedConfig).isNotNull();
|
||||||
assertThat(combinedConfig.getAllowedOrigins()).containsExactly("https://domain1.com", "https://domain2.com");
|
assertThat(combinedConfig.getAllowedOrigins()).containsExactly("https://domain1.com", "https://domain2.com");
|
||||||
assertThat(combinedConfig.getAllowedHeaders()).containsExactly("header1", "header2");
|
assertThat(combinedConfig.getAllowedHeaders()).containsExactly("header1", "header2");
|
||||||
assertThat(combinedConfig.getExposedHeaders()).containsExactly("header3", "header4");
|
assertThat(combinedConfig.getExposedHeaders()).containsExactly("header3", "header4");
|
||||||
assertThat(combinedConfig.getAllowedMethods()).containsExactly(HttpMethod.GET.name(), HttpMethod.PUT.name());
|
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
|
@Test
|
||||||
public void combine() {
|
public void combine() {
|
||||||
CorsConfiguration config = new CorsConfiguration();
|
CorsConfiguration config = new CorsConfiguration();
|
||||||
config.addAllowedOrigin("https://domain1.com");
|
config.addAllowedOrigin("https://domain1.com");
|
||||||
|
config.addAllowedOriginPattern("http://*.domain1.com");
|
||||||
config.addAllowedHeader("header1");
|
config.addAllowedHeader("header1");
|
||||||
config.addExposedHeader("header3");
|
config.addExposedHeader("header3");
|
||||||
config.addAllowedMethod(HttpMethod.GET.name());
|
config.addAllowedMethod(HttpMethod.GET.name());
|
||||||
config.setMaxAge(123L);
|
config.setMaxAge(123L);
|
||||||
config.setAllowCredentials(true);
|
config.setAllowCredentials(true);
|
||||||
config.addAllowedOriginPattern(".*\\.domain1\\.com");
|
|
||||||
CorsConfiguration other = new CorsConfiguration();
|
CorsConfiguration other = new CorsConfiguration();
|
||||||
other.addAllowedOrigin("https://domain2.com");
|
other.addAllowedOrigin("https://domain2.com");
|
||||||
|
other.addAllowedOriginPattern("http://*.domain2.com");
|
||||||
other.addAllowedHeader("header2");
|
other.addAllowedHeader("header2");
|
||||||
other.addExposedHeader("header4");
|
other.addExposedHeader("header4");
|
||||||
other.addAllowedMethod(HttpMethod.PUT.name());
|
other.addAllowedMethod(HttpMethod.PUT.name());
|
||||||
other.setMaxAge(456L);
|
other.setMaxAge(456L);
|
||||||
other.setAllowCredentials(false);
|
other.setAllowCredentials(false);
|
||||||
other.addAllowedOriginPattern(".*\\.domain2\\.com");
|
|
||||||
config = config.combine(other);
|
config = config.combine(other);
|
||||||
assertThat(config).isNotNull();
|
assertThat(config).isNotNull();
|
||||||
assertThat(config.getAllowedOrigins()).containsExactly("https://domain1.com", "https://domain2.com");
|
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.getMaxAge()).isEqualTo(new Long(456));
|
||||||
assertThat(config).isNotNull();
|
assertThat(config).isNotNull();
|
||||||
assertThat(config.getAllowCredentials()).isFalse();
|
assertThat(config.getAllowCredentials()).isFalse();
|
||||||
assertThat(config.getAllowedOriginPatterns()).containsExactly(".*\\.domain1\\.com", ".*\\.domain2\\.com");
|
assertThat(config.getAllowedOriginPatterns()).containsExactly("http://*.domain1.com", "http://*.domain2.com");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void checkOriginAllowed() {
|
public void checkOriginAllowed() {
|
||||||
CorsConfiguration config = new CorsConfiguration();
|
CorsConfiguration config = new CorsConfiguration();
|
||||||
config.setAllowedOrigins(Collections.singletonList("*"));
|
config.addAllowedOrigin("*");
|
||||||
assertThat(config.checkOrigin("https://domain.com")).isEqualTo("*");
|
assertThat(config.checkOrigin("https://domain.com")).isEqualTo("*");
|
||||||
|
|
||||||
config.setAllowCredentials(true);
|
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"));
|
config.setAllowedOrigins(Collections.singletonList("https://domain.com"));
|
||||||
assertThat(config.checkOrigin("https://domain.com")).isEqualTo("https://domain.com");
|
assertThat(config.checkOrigin("https://domain.com")).isEqualTo("https://domain.com");
|
||||||
|
|
||||||
config.setAllowCredentials(false);
|
config.setAllowCredentials(false);
|
||||||
assertThat(config.checkOrigin("https://domain.com")).isEqualTo("https://domain.com");
|
assertThat(config.checkOrigin("https://domain.com")).isEqualTo("https://domain.com");
|
||||||
}
|
}
|
||||||
@@ -291,10 +304,13 @@ public class CorsConfigurationTests {
|
|||||||
CorsConfiguration config = new CorsConfiguration();
|
CorsConfiguration config = new CorsConfiguration();
|
||||||
assertThat(config.checkOrigin(null)).isNull();
|
assertThat(config.checkOrigin(null)).isNull();
|
||||||
assertThat(config.checkOrigin("https://domain.com")).isNull();
|
assertThat(config.checkOrigin("https://domain.com")).isNull();
|
||||||
|
|
||||||
config.addAllowedOrigin("*");
|
config.addAllowedOrigin("*");
|
||||||
assertThat(config.checkOrigin(null)).isNull();
|
assertThat(config.checkOrigin(null)).isNull();
|
||||||
|
|
||||||
config.setAllowedOrigins(Collections.singletonList("https://domain1.com"));
|
config.setAllowedOrigins(Collections.singletonList("https://domain1.com"));
|
||||||
assertThat(config.checkOrigin("https://domain2.com")).isNull();
|
assertThat(config.checkOrigin("https://domain2.com")).isNull();
|
||||||
|
|
||||||
config.setAllowedOrigins(new ArrayList<>());
|
config.setAllowedOrigins(new ArrayList<>());
|
||||||
assertThat(config.checkOrigin("https://domain.com")).isNull();
|
assertThat(config.checkOrigin("https://domain.com")).isNull();
|
||||||
}
|
}
|
||||||
@@ -302,12 +318,17 @@ public class CorsConfigurationTests {
|
|||||||
@Test
|
@Test
|
||||||
public void checkOriginPatternAllowed() {
|
public void checkOriginPatternAllowed() {
|
||||||
CorsConfiguration config = new CorsConfiguration();
|
CorsConfiguration config = new CorsConfiguration();
|
||||||
config.setAllowedOriginPatterns(Collections.singletonList(".*"));
|
assertThat(config.checkOrigin("https://domain.com")).isNull();
|
||||||
|
|
||||||
|
config.applyPermitDefaultValues();
|
||||||
assertThat(config.checkOrigin("https://domain.com")).isEqualTo("*");
|
assertThat(config.checkOrigin("https://domain.com")).isEqualTo("*");
|
||||||
|
|
||||||
config.setAllowCredentials(true);
|
config.setAllowCredentials(true);
|
||||||
assertThat(config.checkOrigin("https://domain.com")).isEqualTo("https://domain.com");
|
assertThatIllegalArgumentException().isThrownBy(() -> config.checkOrigin("https://domain.com"));
|
||||||
config.setAllowedOriginPatterns(Collections.singletonList(".*\\.domain\\.com"));
|
|
||||||
|
config.addAllowedOriginPattern("https://*.domain.com");
|
||||||
assertThat(config.checkOrigin("https://example.domain.com")).isEqualTo("https://example.domain.com");
|
assertThat(config.checkOrigin("https://example.domain.com")).isEqualTo("https://example.domain.com");
|
||||||
|
|
||||||
config.setAllowCredentials(false);
|
config.setAllowCredentials(false);
|
||||||
assertThat(config.checkOrigin("https://example.domain.com")).isEqualTo("https://example.domain.com");
|
assertThat(config.checkOrigin("https://example.domain.com")).isEqualTo("https://example.domain.com");
|
||||||
}
|
}
|
||||||
@@ -317,10 +338,12 @@ public class CorsConfigurationTests {
|
|||||||
CorsConfiguration config = new CorsConfiguration();
|
CorsConfiguration config = new CorsConfiguration();
|
||||||
assertThat(config.checkOrigin(null)).isNull();
|
assertThat(config.checkOrigin(null)).isNull();
|
||||||
assertThat(config.checkOrigin("https://domain.com")).isNull();
|
assertThat(config.checkOrigin("https://domain.com")).isNull();
|
||||||
config.addAllowedOriginPattern(".*");
|
config.addAllowedOriginPattern("*");
|
||||||
assertThat(config.checkOrigin(null)).isNull();
|
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();
|
assertThat(config.checkOrigin("https://domain2.com")).isNull();
|
||||||
|
|
||||||
config.setAllowedOriginPatterns(new ArrayList<>());
|
config.setAllowedOriginPatterns(new ArrayList<>());
|
||||||
assertThat(config.checkOrigin("https://domain.com")).isNull();
|
assertThat(config.checkOrigin("https://domain.com")).isNull();
|
||||||
}
|
}
|
||||||
@@ -329,8 +352,10 @@ public class CorsConfigurationTests {
|
|||||||
public void checkMethodAllowed() {
|
public void checkMethodAllowed() {
|
||||||
CorsConfiguration config = new CorsConfiguration();
|
CorsConfiguration config = new CorsConfiguration();
|
||||||
assertThat(config.checkHttpMethod(HttpMethod.GET)).containsExactly(HttpMethod.GET, HttpMethod.HEAD);
|
assertThat(config.checkHttpMethod(HttpMethod.GET)).containsExactly(HttpMethod.GET, HttpMethod.HEAD);
|
||||||
|
|
||||||
config.addAllowedMethod("GET");
|
config.addAllowedMethod("GET");
|
||||||
assertThat(config.checkHttpMethod(HttpMethod.GET)).containsExactly(HttpMethod.GET);
|
assertThat(config.checkHttpMethod(HttpMethod.GET)).containsExactly(HttpMethod.GET);
|
||||||
|
|
||||||
config.addAllowedMethod("POST");
|
config.addAllowedMethod("POST");
|
||||||
assertThat(config.checkHttpMethod(HttpMethod.GET)).containsExactly(HttpMethod.GET, HttpMethod.POST);
|
assertThat(config.checkHttpMethod(HttpMethod.GET)).containsExactly(HttpMethod.GET, HttpMethod.POST);
|
||||||
assertThat(config.checkHttpMethod(HttpMethod.POST)).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();
|
CorsConfiguration config = new CorsConfiguration();
|
||||||
assertThat(config.checkHttpMethod(null)).isNull();
|
assertThat(config.checkHttpMethod(null)).isNull();
|
||||||
assertThat(config.checkHttpMethod(HttpMethod.DELETE)).isNull();
|
assertThat(config.checkHttpMethod(HttpMethod.DELETE)).isNull();
|
||||||
|
|
||||||
config.setAllowedMethods(new ArrayList<>());
|
config.setAllowedMethods(new ArrayList<>());
|
||||||
assertThat(config.checkHttpMethod(HttpMethod.POST)).isNull();
|
assertThat(config.checkHttpMethod(HttpMethod.POST)).isNull();
|
||||||
}
|
}
|
||||||
@@ -349,8 +375,10 @@ public class CorsConfigurationTests {
|
|||||||
public void checkHeadersAllowed() {
|
public void checkHeadersAllowed() {
|
||||||
CorsConfiguration config = new CorsConfiguration();
|
CorsConfiguration config = new CorsConfiguration();
|
||||||
assertThat(config.checkHeaders(Collections.emptyList())).isEqualTo(Collections.emptyList());
|
assertThat(config.checkHeaders(Collections.emptyList())).isEqualTo(Collections.emptyList());
|
||||||
|
|
||||||
config.addAllowedHeader("header1");
|
config.addAllowedHeader("header1");
|
||||||
config.addAllowedHeader("header2");
|
config.addAllowedHeader("header2");
|
||||||
|
|
||||||
assertThat(config.checkHeaders(Collections.singletonList("header1"))).containsExactly("header1");
|
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"))).containsExactly("header1", "header2");
|
||||||
assertThat(config.checkHeaders(Arrays.asList("header1", "header2", "header3"))).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();
|
CorsConfiguration config = new CorsConfiguration();
|
||||||
assertThat(config.checkHeaders(null)).isNull();
|
assertThat(config.checkHeaders(null)).isNull();
|
||||||
assertThat(config.checkHeaders(Collections.singletonList("header1"))).isNull();
|
assertThat(config.checkHeaders(Collections.singletonList("header1"))).isNull();
|
||||||
|
|
||||||
config.setAllowedHeaders(Collections.emptyList());
|
config.setAllowedHeaders(Collections.emptyList());
|
||||||
assertThat(config.checkHeaders(Collections.singletonList("header1"))).isNull();
|
assertThat(config.checkHeaders(Collections.singletonList("header1"))).isNull();
|
||||||
|
|
||||||
config.addAllowedHeader("header2");
|
config.addAllowedHeader("header2");
|
||||||
config.addAllowedHeader("header3");
|
config.addAllowedHeader("header3");
|
||||||
assertThat(config.checkHeaders(Collections.singletonList("header1"))).isNull();
|
assertThat(config.checkHeaders(Collections.singletonList("header1"))).isNull();
|
||||||
@@ -374,6 +404,7 @@ public class CorsConfigurationTests {
|
|||||||
config.addAllowedOrigin("https://domain.com");
|
config.addAllowedOrigin("https://domain.com");
|
||||||
config.addAllowedHeader("header1");
|
config.addAllowedHeader("header1");
|
||||||
config.addAllowedMethod("PATCH");
|
config.addAllowedMethod("PATCH");
|
||||||
|
|
||||||
assertThat(config.getAllowedOrigins()).containsExactly("*", "https://domain.com");
|
assertThat(config.getAllowedOrigins()).containsExactly("*", "https://domain.com");
|
||||||
assertThat(config.getAllowedHeaders()).containsExactly("*", "header1");
|
assertThat(config.getAllowedHeaders()).containsExactly("*", "header1");
|
||||||
assertThat(config.getAllowedMethods()).containsExactly("GET", "HEAD", "POST", "PATCH");
|
assertThat(config.getAllowedMethods()).containsExactly("GET", "HEAD", "POST", "PATCH");
|
||||||
@@ -382,9 +413,10 @@ public class CorsConfigurationTests {
|
|||||||
@Test
|
@Test
|
||||||
public void permitDefaultDoesntSetOriginWhenPatternPresent() {
|
public void permitDefaultDoesntSetOriginWhenPatternPresent() {
|
||||||
CorsConfiguration config = new CorsConfiguration();
|
CorsConfiguration config = new CorsConfiguration();
|
||||||
config.addAllowedOriginPattern(".*\\.com");
|
config.addAllowedOriginPattern("http://*.com");
|
||||||
config = config.applyPermitDefaultValues();
|
config = config.applyPermitDefaultValues();
|
||||||
|
|
||||||
assertThat(config.getAllowedOrigins()).isNull();
|
assertThat(config.getAllowedOrigins()).isNull();
|
||||||
assertThat(config.getAllowedOriginPatterns()).containsExactly(".*\\.com");
|
assertThat(config.getAllowedOriginPatterns()).containsExactly("http://*.com");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,6 +16,8 @@
|
|||||||
|
|
||||||
package org.springframework.web.cors;
|
package org.springframework.web.cors;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
|
||||||
import javax.servlet.http.HttpServletResponse;
|
import javax.servlet.http.HttpServletResponse;
|
||||||
|
|
||||||
import org.junit.jupiter.api.BeforeEach;
|
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 org.springframework.web.testfixture.servlet.MockHttpServletResponse;
|
||||||
|
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
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.
|
* Test {@link DefaultCorsProcessor} with simple or preflight CORS request.
|
||||||
@@ -138,11 +141,17 @@ public class DefaultCorsProcessorTests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void actualRequestCredentialsWithOriginWildcard() throws Exception {
|
public void actualRequestCredentialsWithWildcardOrigin() throws Exception {
|
||||||
this.request.setMethod(HttpMethod.GET.name());
|
this.request.setMethod(HttpMethod.GET.name());
|
||||||
this.request.addHeader(HttpHeaders.ORIGIN, "https://domain2.com");
|
this.request.addHeader(HttpHeaders.ORIGIN, "https://domain2.com");
|
||||||
|
|
||||||
this.conf.addAllowedOrigin("*");
|
this.conf.addAllowedOrigin("*");
|
||||||
this.conf.setAllowCredentials(true);
|
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);
|
this.processor.processRequest(this.conf, this.request, this.response);
|
||||||
assertThat(this.response.containsHeader(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN)).isTrue();
|
assertThat(this.response.containsHeader(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN)).isTrue();
|
||||||
@@ -311,17 +320,21 @@ public class DefaultCorsProcessorTests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void preflightRequestCredentialsWithOriginWildcard() throws Exception {
|
public void preflightRequestCredentialsWithWildcardOrigin() throws Exception {
|
||||||
this.request.setMethod(HttpMethod.OPTIONS.name());
|
this.request.setMethod(HttpMethod.OPTIONS.name());
|
||||||
this.request.addHeader(HttpHeaders.ORIGIN, "https://domain2.com");
|
this.request.addHeader(HttpHeaders.ORIGIN, "https://domain2.com");
|
||||||
this.request.addHeader(HttpHeaders.ACCESS_CONTROL_REQUEST_METHOD, "GET");
|
this.request.addHeader(HttpHeaders.ACCESS_CONTROL_REQUEST_METHOD, "GET");
|
||||||
this.request.addHeader(HttpHeaders.ACCESS_CONTROL_REQUEST_HEADERS, "Header1");
|
this.request.addHeader(HttpHeaders.ACCESS_CONTROL_REQUEST_HEADERS, "Header1");
|
||||||
this.conf.addAllowedOrigin("https://domain1.com");
|
this.conf.setAllowedOrigins(Arrays.asList("https://domain1.com", "*", "http://domain3.example"));
|
||||||
this.conf.addAllowedOrigin("*");
|
|
||||||
this.conf.addAllowedOrigin("http://domain3.example");
|
|
||||||
this.conf.addAllowedHeader("Header1");
|
this.conf.addAllowedHeader("Header1");
|
||||||
this.conf.setAllowCredentials(true);
|
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);
|
this.processor.processRequest(this.conf, this.request, this.response);
|
||||||
assertThat(this.response.containsHeader(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN)).isTrue();
|
assertThat(this.response.containsHeader(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN)).isTrue();
|
||||||
assertThat(this.response.getHeader(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN)).isEqualTo("https://domain2.com");
|
assertThat(this.response.getHeader(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN)).isEqualTo("https://domain2.com");
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ import org.springframework.web.testfixture.http.server.reactive.MockServerHttpRe
|
|||||||
import org.springframework.web.testfixture.server.MockServerWebExchange;
|
import org.springframework.web.testfixture.server.MockServerWebExchange;
|
||||||
|
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
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_HEADERS;
|
||||||
import static org.springframework.http.HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN;
|
import static org.springframework.http.HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN;
|
||||||
import static org.springframework.http.HttpHeaders.ACCESS_CONTROL_REQUEST_HEADERS;
|
import static org.springframework.http.HttpHeaders.ACCESS_CONTROL_REQUEST_HEADERS;
|
||||||
@@ -56,7 +57,7 @@ public class DefaultCorsProcessorTests {
|
|||||||
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void requestWithoutOriginHeader() throws Exception {
|
public void requestWithoutOriginHeader() {
|
||||||
MockServerHttpRequest request = MockServerHttpRequest
|
MockServerHttpRequest request = MockServerHttpRequest
|
||||||
.method(HttpMethod.GET, "http://domain1.example/test.html")
|
.method(HttpMethod.GET, "http://domain1.example/test.html")
|
||||||
.build();
|
.build();
|
||||||
@@ -71,7 +72,7 @@ public class DefaultCorsProcessorTests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void sameOriginRequest() throws Exception {
|
public void sameOriginRequest() {
|
||||||
MockServerHttpRequest request = MockServerHttpRequest
|
MockServerHttpRequest request = MockServerHttpRequest
|
||||||
.method(HttpMethod.GET, "http://domain1.example/test.html")
|
.method(HttpMethod.GET, "http://domain1.example/test.html")
|
||||||
.header(HttpHeaders.ORIGIN, "http://domain1.example")
|
.header(HttpHeaders.ORIGIN, "http://domain1.example")
|
||||||
@@ -87,7 +88,7 @@ public class DefaultCorsProcessorTests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void actualRequestWithOriginHeader() throws Exception {
|
public void actualRequestWithOriginHeader() {
|
||||||
ServerWebExchange exchange = actualRequest();
|
ServerWebExchange exchange = actualRequest();
|
||||||
this.processor.process(this.conf, exchange);
|
this.processor.process(this.conf, exchange);
|
||||||
|
|
||||||
@@ -99,7 +100,7 @@ public class DefaultCorsProcessorTests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void actualRequestWithOriginHeaderAndNullConfig() throws Exception {
|
public void actualRequestWithOriginHeaderAndNullConfig() {
|
||||||
ServerWebExchange exchange = actualRequest();
|
ServerWebExchange exchange = actualRequest();
|
||||||
this.processor.process(null, exchange);
|
this.processor.process(null, exchange);
|
||||||
|
|
||||||
@@ -109,7 +110,7 @@ public class DefaultCorsProcessorTests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void actualRequestWithOriginHeaderAndAllowedOrigin() throws Exception {
|
public void actualRequestWithOriginHeaderAndAllowedOrigin() {
|
||||||
ServerWebExchange exchange = actualRequest();
|
ServerWebExchange exchange = actualRequest();
|
||||||
this.conf.addAllowedOrigin("*");
|
this.conf.addAllowedOrigin("*");
|
||||||
this.processor.process(this.conf, exchange);
|
this.processor.process(this.conf, exchange);
|
||||||
@@ -125,7 +126,7 @@ public class DefaultCorsProcessorTests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void actualRequestCredentials() throws Exception {
|
public void actualRequestCredentials() {
|
||||||
ServerWebExchange exchange = actualRequest();
|
ServerWebExchange exchange = actualRequest();
|
||||||
this.conf.addAllowedOrigin("https://domain1.com");
|
this.conf.addAllowedOrigin("https://domain1.com");
|
||||||
this.conf.addAllowedOrigin("https://domain2.com");
|
this.conf.addAllowedOrigin("https://domain2.com");
|
||||||
@@ -144,10 +145,14 @@ public class DefaultCorsProcessorTests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void actualRequestCredentialsWithOriginWildcard() throws Exception {
|
public void actualRequestCredentialsWithWildcardOrigin() {
|
||||||
ServerWebExchange exchange = actualRequest();
|
ServerWebExchange exchange = actualRequest();
|
||||||
this.conf.addAllowedOrigin("*");
|
this.conf.addAllowedOrigin("*");
|
||||||
this.conf.setAllowCredentials(true);
|
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);
|
this.processor.process(this.conf, exchange);
|
||||||
|
|
||||||
ServerHttpResponse response = exchange.getResponse();
|
ServerHttpResponse response = exchange.getResponse();
|
||||||
@@ -161,7 +166,7 @@ public class DefaultCorsProcessorTests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void actualRequestCaseInsensitiveOriginMatch() throws Exception {
|
public void actualRequestCaseInsensitiveOriginMatch() {
|
||||||
ServerWebExchange exchange = actualRequest();
|
ServerWebExchange exchange = actualRequest();
|
||||||
this.conf.addAllowedOrigin("https://DOMAIN2.com");
|
this.conf.addAllowedOrigin("https://DOMAIN2.com");
|
||||||
this.processor.process(this.conf, exchange);
|
this.processor.process(this.conf, exchange);
|
||||||
@@ -174,7 +179,7 @@ public class DefaultCorsProcessorTests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void actualRequestExposedHeaders() throws Exception {
|
public void actualRequestExposedHeaders() {
|
||||||
ServerWebExchange exchange = actualRequest();
|
ServerWebExchange exchange = actualRequest();
|
||||||
this.conf.addExposedHeader("header1");
|
this.conf.addExposedHeader("header1");
|
||||||
this.conf.addExposedHeader("header2");
|
this.conf.addExposedHeader("header2");
|
||||||
@@ -193,7 +198,7 @@ public class DefaultCorsProcessorTests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void preflightRequestAllOriginsAllowed() throws Exception {
|
public void preflightRequestAllOriginsAllowed() {
|
||||||
ServerWebExchange exchange = MockServerWebExchange.from(
|
ServerWebExchange exchange = MockServerWebExchange.from(
|
||||||
preFlightRequest().header(ACCESS_CONTROL_REQUEST_METHOD, "GET"));
|
preFlightRequest().header(ACCESS_CONTROL_REQUEST_METHOD, "GET"));
|
||||||
this.conf.addAllowedOrigin("*");
|
this.conf.addAllowedOrigin("*");
|
||||||
@@ -207,7 +212,7 @@ public class DefaultCorsProcessorTests {
|
|||||||
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void preflightRequestWrongAllowedMethod() throws Exception {
|
public void preflightRequestWrongAllowedMethod() {
|
||||||
ServerWebExchange exchange = MockServerWebExchange.from(
|
ServerWebExchange exchange = MockServerWebExchange.from(
|
||||||
preFlightRequest().header(ACCESS_CONTROL_REQUEST_METHOD, "DELETE"));
|
preFlightRequest().header(ACCESS_CONTROL_REQUEST_METHOD, "DELETE"));
|
||||||
this.conf.addAllowedOrigin("*");
|
this.conf.addAllowedOrigin("*");
|
||||||
@@ -220,7 +225,7 @@ public class DefaultCorsProcessorTests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void preflightRequestMatchedAllowedMethod() throws Exception {
|
public void preflightRequestMatchedAllowedMethod() {
|
||||||
ServerWebExchange exchange = MockServerWebExchange.from(
|
ServerWebExchange exchange = MockServerWebExchange.from(
|
||||||
preFlightRequest().header(ACCESS_CONTROL_REQUEST_METHOD, "GET"));
|
preFlightRequest().header(ACCESS_CONTROL_REQUEST_METHOD, "GET"));
|
||||||
this.conf.addAllowedOrigin("*");
|
this.conf.addAllowedOrigin("*");
|
||||||
@@ -234,7 +239,7 @@ public class DefaultCorsProcessorTests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void preflightRequestTestWithOriginButWithoutOtherHeaders() throws Exception {
|
public void preflightRequestTestWithOriginButWithoutOtherHeaders() {
|
||||||
ServerWebExchange exchange = MockServerWebExchange.from(preFlightRequest());
|
ServerWebExchange exchange = MockServerWebExchange.from(preFlightRequest());
|
||||||
this.processor.process(this.conf, exchange);
|
this.processor.process(this.conf, exchange);
|
||||||
|
|
||||||
@@ -246,7 +251,7 @@ public class DefaultCorsProcessorTests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void preflightRequestWithoutRequestMethod() throws Exception {
|
public void preflightRequestWithoutRequestMethod() {
|
||||||
ServerWebExchange exchange = MockServerWebExchange.from(
|
ServerWebExchange exchange = MockServerWebExchange.from(
|
||||||
preFlightRequest().header(ACCESS_CONTROL_REQUEST_HEADERS, "Header1"));
|
preFlightRequest().header(ACCESS_CONTROL_REQUEST_HEADERS, "Header1"));
|
||||||
this.processor.process(this.conf, exchange);
|
this.processor.process(this.conf, exchange);
|
||||||
@@ -259,7 +264,7 @@ public class DefaultCorsProcessorTests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void preflightRequestWithRequestAndMethodHeaderButNoConfig() throws Exception {
|
public void preflightRequestWithRequestAndMethodHeaderButNoConfig() {
|
||||||
ServerWebExchange exchange = MockServerWebExchange.from(preFlightRequest()
|
ServerWebExchange exchange = MockServerWebExchange.from(preFlightRequest()
|
||||||
.header(ACCESS_CONTROL_REQUEST_METHOD, "GET")
|
.header(ACCESS_CONTROL_REQUEST_METHOD, "GET")
|
||||||
.header(ACCESS_CONTROL_REQUEST_HEADERS, "Header1"));
|
.header(ACCESS_CONTROL_REQUEST_HEADERS, "Header1"));
|
||||||
@@ -274,7 +279,7 @@ public class DefaultCorsProcessorTests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void preflightRequestValidRequestAndConfig() throws Exception {
|
public void preflightRequestValidRequestAndConfig() {
|
||||||
ServerWebExchange exchange = MockServerWebExchange.from(preFlightRequest()
|
ServerWebExchange exchange = MockServerWebExchange.from(preFlightRequest()
|
||||||
.header(ACCESS_CONTROL_REQUEST_METHOD, "GET")
|
.header(ACCESS_CONTROL_REQUEST_METHOD, "GET")
|
||||||
.header(ACCESS_CONTROL_REQUEST_HEADERS, "Header1"));
|
.header(ACCESS_CONTROL_REQUEST_HEADERS, "Header1"));
|
||||||
@@ -299,7 +304,7 @@ public class DefaultCorsProcessorTests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void preflightRequestCredentials() throws Exception {
|
public void preflightRequestCredentials() {
|
||||||
ServerWebExchange exchange = MockServerWebExchange.from(preFlightRequest()
|
ServerWebExchange exchange = MockServerWebExchange.from(preFlightRequest()
|
||||||
.header(ACCESS_CONTROL_REQUEST_METHOD, "GET")
|
.header(ACCESS_CONTROL_REQUEST_METHOD, "GET")
|
||||||
.header(ACCESS_CONTROL_REQUEST_HEADERS, "Header1"));
|
.header(ACCESS_CONTROL_REQUEST_HEADERS, "Header1"));
|
||||||
@@ -323,7 +328,7 @@ public class DefaultCorsProcessorTests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void preflightRequestCredentialsWithOriginWildcard() throws Exception {
|
public void preflightRequestCredentialsWithWildcardOrigin() {
|
||||||
ServerWebExchange exchange = MockServerWebExchange.from(preFlightRequest()
|
ServerWebExchange exchange = MockServerWebExchange.from(preFlightRequest()
|
||||||
.header(ACCESS_CONTROL_REQUEST_METHOD, "GET")
|
.header(ACCESS_CONTROL_REQUEST_METHOD, "GET")
|
||||||
.header(ACCESS_CONTROL_REQUEST_HEADERS, "Header1"));
|
.header(ACCESS_CONTROL_REQUEST_HEADERS, "Header1"));
|
||||||
@@ -333,7 +338,10 @@ public class DefaultCorsProcessorTests {
|
|||||||
this.conf.addAllowedOrigin("http://domain3.example");
|
this.conf.addAllowedOrigin("http://domain3.example");
|
||||||
this.conf.addAllowedHeader("Header1");
|
this.conf.addAllowedHeader("Header1");
|
||||||
this.conf.setAllowCredentials(true);
|
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);
|
this.processor.process(this.conf, exchange);
|
||||||
|
|
||||||
ServerHttpResponse response = exchange.getResponse();
|
ServerHttpResponse response = exchange.getResponse();
|
||||||
@@ -345,7 +353,7 @@ public class DefaultCorsProcessorTests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void preflightRequestAllowedHeaders() throws Exception {
|
public void preflightRequestAllowedHeaders() {
|
||||||
ServerWebExchange exchange = MockServerWebExchange.from(preFlightRequest()
|
ServerWebExchange exchange = MockServerWebExchange.from(preFlightRequest()
|
||||||
.header(ACCESS_CONTROL_REQUEST_METHOD, "GET")
|
.header(ACCESS_CONTROL_REQUEST_METHOD, "GET")
|
||||||
.header(ACCESS_CONTROL_REQUEST_HEADERS, "Header1, Header2"));
|
.header(ACCESS_CONTROL_REQUEST_HEADERS, "Header1, Header2"));
|
||||||
@@ -369,7 +377,7 @@ public class DefaultCorsProcessorTests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void preflightRequestAllowsAllHeaders() throws Exception {
|
public void preflightRequestAllowsAllHeaders() {
|
||||||
ServerWebExchange exchange = MockServerWebExchange.from(preFlightRequest()
|
ServerWebExchange exchange = MockServerWebExchange.from(preFlightRequest()
|
||||||
.header(ACCESS_CONTROL_REQUEST_METHOD, "GET")
|
.header(ACCESS_CONTROL_REQUEST_METHOD, "GET")
|
||||||
.header(ACCESS_CONTROL_REQUEST_HEADERS, "Header1, Header2"));
|
.header(ACCESS_CONTROL_REQUEST_HEADERS, "Header1, Header2"));
|
||||||
@@ -391,7 +399,7 @@ public class DefaultCorsProcessorTests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void preflightRequestWithEmptyHeaders() throws Exception {
|
public void preflightRequestWithEmptyHeaders() {
|
||||||
ServerWebExchange exchange = MockServerWebExchange.from(preFlightRequest()
|
ServerWebExchange exchange = MockServerWebExchange.from(preFlightRequest()
|
||||||
.header(ACCESS_CONTROL_REQUEST_METHOD, "GET")
|
.header(ACCESS_CONTROL_REQUEST_METHOD, "GET")
|
||||||
.header(ACCESS_CONTROL_REQUEST_HEADERS, ""));
|
.header(ACCESS_CONTROL_REQUEST_HEADERS, ""));
|
||||||
|
|||||||
@@ -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");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with 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.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
import org.springframework.web.cors.CorsConfiguration;
|
import org.springframework.web.cors.CorsConfiguration;
|
||||||
|
|
||||||
@@ -44,24 +45,28 @@ public class CorsRegistration {
|
|||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The list of allowed origins that be specific origins, e.g.
|
* A list of origins for which cross-origin requests are allowed. Please,
|
||||||
* {@code "https://domain1.com"}, or {@code "*"} for all origins.
|
* see {@link CorsConfiguration#setAllowedOrigins(List)} for details.
|
||||||
* <p>A matched origin is listed in the {@code Access-Control-Allow-Origin}
|
* <p>By default all origins are allowed unless {@code originPatterns} is
|
||||||
* response header of preflight actual CORS requests.
|
* also set in which case {@code originPatterns} is used instead.
|
||||||
* <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.
|
|
||||||
*/
|
*/
|
||||||
public CorsRegistration allowedOrigins(String... origins) {
|
public CorsRegistration allowedOrigins(String... origins) {
|
||||||
this.config.setAllowedOrigins(new ArrayList<>(Arrays.asList(origins)));
|
this.config.setAllowedOrigins(new ArrayList<>(Arrays.asList(origins)));
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Alternative to {@link #allowCredentials} that supports origins declared
|
||||||
|
* via wildcard patterns. Please, see
|
||||||
|
* @link CorsConfiguration#setAllowedOriginPatterns(List)} for details.
|
||||||
|
* <p>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.
|
* Set the HTTP methods to allow, e.g. {@code "GET"}, {@code "POST"}, etc.
|
||||||
* <p>The special value {@code "*"} allows all methods.
|
* <p>The special value {@code "*"} allows all methods.
|
||||||
|
|||||||
@@ -187,6 +187,9 @@ public abstract class AbstractHandlerMapping extends ApplicationObjectSupport
|
|||||||
CorsConfiguration config = (this.corsConfigurationSource != null ? this.corsConfigurationSource.getCorsConfiguration(exchange) : null);
|
CorsConfiguration config = (this.corsConfigurationSource != null ? this.corsConfigurationSource.getCorsConfiguration(exchange) : null);
|
||||||
CorsConfiguration handlerConfig = getCorsConfiguration(handler, exchange);
|
CorsConfiguration handlerConfig = getCorsConfiguration(handler, exchange);
|
||||||
config = (config != null ? config.combine(handlerConfig) : handlerConfig);
|
config = (config != null ? config.combine(handlerConfig) : handlerConfig);
|
||||||
|
if (config != null) {
|
||||||
|
config.validateAllowCredentials();
|
||||||
|
}
|
||||||
if (!this.corsProcessor.process(config, exchange) || CorsUtils.isPreFlightRequest(request)) {
|
if (!this.corsProcessor.process(config, exchange) || CorsUtils.isPreFlightRequest(request)) {
|
||||||
return REQUEST_HANDLED_HANDLER;
|
return REQUEST_HANDLED_HANDLER;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -87,7 +87,7 @@ public abstract class AbstractHandlerMethodMapping<T> extends AbstractHandlerMap
|
|||||||
private static final CorsConfiguration ALLOW_CORS_CONFIG = new CorsConfiguration();
|
private static final CorsConfiguration ALLOW_CORS_CONFIG = new CorsConfiguration();
|
||||||
|
|
||||||
static {
|
static {
|
||||||
ALLOW_CORS_CONFIG.addAllowedOrigin("*");
|
ALLOW_CORS_CONFIG.addAllowedOriginPattern("*");
|
||||||
ALLOW_CORS_CONFIG.addAllowedMethod("*");
|
ALLOW_CORS_CONFIG.addAllowedMethod("*");
|
||||||
ALLOW_CORS_CONFIG.addAllowedHeader("*");
|
ALLOW_CORS_CONFIG.addAllowedHeader("*");
|
||||||
ALLOW_CORS_CONFIG.setAllowCredentials(true);
|
ALLOW_CORS_CONFIG.setAllowCredentials(true);
|
||||||
@@ -485,9 +485,10 @@ public abstract class AbstractHandlerMethodMapping<T> extends AbstractHandlerMap
|
|||||||
validateMethodMapping(handlerMethod, mapping);
|
validateMethodMapping(handlerMethod, mapping);
|
||||||
this.mappingLookup.put(mapping, handlerMethod);
|
this.mappingLookup.put(mapping, handlerMethod);
|
||||||
|
|
||||||
CorsConfiguration corsConfig = initCorsConfiguration(handler, method, mapping);
|
CorsConfiguration config = initCorsConfiguration(handler, method, mapping);
|
||||||
if (corsConfig != null) {
|
if (config != null) {
|
||||||
this.corsLookup.put(handlerMethod, corsConfig);
|
config.validateAllowCredentials();
|
||||||
|
this.corsLookup.put(handlerMethod, config);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.registry.put(mapping, new MappingRegistration<>(mapping, handlerMethod));
|
this.registry.put(mapping, new MappingRegistration<>(mapping, handlerMethod));
|
||||||
|
|||||||
@@ -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");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
@@ -17,6 +17,7 @@
|
|||||||
package org.springframework.web.reactive.config;
|
package org.springframework.web.reactive.config;
|
||||||
|
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
import java.util.Collections;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
@@ -56,11 +57,20 @@ public class CorsRegistryTests {
|
|||||||
assertThat(configs.size()).isEqualTo(1);
|
assertThat(configs.size()).isEqualTo(1);
|
||||||
CorsConfiguration config = configs.get("/foo");
|
CorsConfiguration config = configs.get("/foo");
|
||||||
assertThat(config.getAllowedOrigins()).isEqualTo(Arrays.asList("https://domain2.com", "https://domain2.com"));
|
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.getAllowedHeaders()).isEqualTo(Arrays.asList("header1", "header2"));
|
||||||
assertThat(config.getExposedHeaders()).isEqualTo(Arrays.asList("header3", "header4"));
|
assertThat(config.getExposedHeaders()).isEqualTo(Arrays.asList("header3", "header4"));
|
||||||
assertThat(config.getAllowCredentials()).isEqualTo(false);
|
assertThat(config.getAllowCredentials()).isEqualTo(false);
|
||||||
assertThat(config.getMaxAge()).isEqualTo(Long.valueOf(3600));
|
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("*");
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -113,7 +113,7 @@ public class CorsUrlHandlerMappingTests {
|
|||||||
@Test
|
@Test
|
||||||
public void actualRequestWithGlobalPatternCorsConfig() throws Exception {
|
public void actualRequestWithGlobalPatternCorsConfig() throws Exception {
|
||||||
CorsConfiguration mappedConfig = new CorsConfiguration();
|
CorsConfiguration mappedConfig = new CorsConfiguration();
|
||||||
mappedConfig.addAllowedOriginPattern(".*\\.domain2.com");
|
mappedConfig.addAllowedOriginPattern("https://*.domain2.com");
|
||||||
this.handlerMapping.setCorsConfigurations(Collections.singletonMap("/welcome.html", mappedConfig));
|
this.handlerMapping.setCorsConfigurations(Collections.singletonMap("/welcome.html", mappedConfig));
|
||||||
|
|
||||||
String origin = "https://example.domain2.com";
|
String origin = "https://example.domain2.com";
|
||||||
@@ -122,7 +122,8 @@ public class CorsUrlHandlerMappingTests {
|
|||||||
|
|
||||||
assertThat(actual).isNotNull();
|
assertThat(actual).isNotNull();
|
||||||
assertThat(actual).isSameAs(this.welcomeController);
|
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
|
@Test
|
||||||
@@ -197,7 +198,7 @@ public class CorsUrlHandlerMappingTests {
|
|||||||
@Override
|
@Override
|
||||||
public CorsConfiguration getCorsConfiguration(ServerWebExchange exchange) {
|
public CorsConfiguration getCorsConfiguration(ServerWebExchange exchange) {
|
||||||
CorsConfiguration config = new CorsConfiguration();
|
CorsConfiguration config = new CorsConfiguration();
|
||||||
config.addAllowedOrigin("*");
|
config.addAllowedOriginPattern("*");
|
||||||
config.setAllowCredentials(true);
|
config.setAllowCredentials(true);
|
||||||
return config;
|
return config;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -68,7 +68,7 @@ class CrossOriginAnnotationIntegrationTests extends AbstractRequestMappingIntegr
|
|||||||
context.register(WebConfig.class);
|
context.register(WebConfig.class);
|
||||||
Properties props = new Properties();
|
Properties props = new Properties();
|
||||||
props.setProperty("myOrigin", "https://site1.com");
|
props.setProperty("myOrigin", "https://site1.com");
|
||||||
props.setProperty("myOriginPattern", ".*\\.com");
|
props.setProperty("myOriginPattern", "https://*.com");
|
||||||
context.getEnvironment().getPropertySources().addFirst(new PropertiesPropertySource("ps", props));
|
context.getEnvironment().getPropertySources().addFirst(new PropertiesPropertySource("ps", props));
|
||||||
context.register(PropertySourcesPlaceholderConfigurer.class);
|
context.register(PropertySourcesPlaceholderConfigurer.class);
|
||||||
context.refresh();
|
context.refresh();
|
||||||
@@ -358,7 +358,7 @@ class CrossOriginAnnotationIntegrationTests extends AbstractRequestMappingIntegr
|
|||||||
return "placeholder";
|
return "placeholder";
|
||||||
}
|
}
|
||||||
|
|
||||||
@CrossOrigin(originPatterns = ".*\\.com")
|
@CrossOrigin(originPatterns = "https://*.com")
|
||||||
@GetMapping("/origin-pattern-value-attribute")
|
@GetMapping("/origin-pattern-value-attribute")
|
||||||
public String customOriginPatternDefinedViaValueAttribute() {
|
public String customOriginPatternDefinedViaValueAttribute() {
|
||||||
return "pattern-value-attribute";
|
return "pattern-value-attribute";
|
||||||
@@ -388,7 +388,7 @@ class CrossOriginAnnotationIntegrationTests extends AbstractRequestMappingIntegr
|
|||||||
return "bar";
|
return "bar";
|
||||||
}
|
}
|
||||||
|
|
||||||
@CrossOrigin(allowCredentials = "true")
|
@CrossOrigin(originPatterns = "*", allowCredentials = "true")
|
||||||
@GetMapping("/baz")
|
@GetMapping("/baz")
|
||||||
public String baz() {
|
public String baz() {
|
||||||
return "baz";
|
return "baz";
|
||||||
|
|||||||
@@ -161,8 +161,7 @@ class GlobalCorsConfigIntegrationTests extends AbstractRequestMappingIntegration
|
|||||||
ResponseEntity<String> entity = performOptions("/ambiguous", this.headers, String.class);
|
ResponseEntity<String> entity = performOptions("/ambiguous", this.headers, String.class);
|
||||||
assertThat(entity.getStatusCode()).isEqualTo(HttpStatus.OK);
|
assertThat(entity.getStatusCode()).isEqualTo(HttpStatus.OK);
|
||||||
assertThat(entity.getHeaders().getAccessControlAllowOrigin()).isEqualTo("http://localhost:9000");
|
assertThat(entity.getHeaders().getAccessControlAllowOrigin()).isEqualTo("http://localhost:9000");
|
||||||
assertThat(entity.getHeaders().getAccessControlAllowMethods())
|
assertThat(entity.getHeaders().getAccessControlAllowMethods()).containsExactly(HttpMethod.GET);
|
||||||
.containsExactly(HttpMethod.GET);
|
|
||||||
assertThat(entity.getHeaders().getAccessControlAllowCredentials()).isEqualTo(true);
|
assertThat(entity.getHeaders().getAccessControlAllowCredentials()).isEqualTo(true);
|
||||||
assertThat(entity.getHeaders().get(HttpHeaders.VARY))
|
assertThat(entity.getHeaders().get(HttpHeaders.VARY))
|
||||||
.containsExactly(HttpHeaders.ORIGIN, HttpHeaders.ACCESS_CONTROL_REQUEST_METHOD,
|
.containsExactly(HttpHeaders.ORIGIN, HttpHeaders.ACCESS_CONTROL_REQUEST_METHOD,
|
||||||
@@ -177,12 +176,9 @@ class GlobalCorsConfigIntegrationTests extends AbstractRequestMappingIntegration
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void addCorsMappings(CorsRegistry registry) {
|
protected void addCorsMappings(CorsRegistry registry) {
|
||||||
registry.addMapping("/cors-restricted")
|
registry.addMapping("/cors-restricted").allowedOrigins("https://foo").allowedMethods("GET", "POST");
|
||||||
.allowedOrigins("https://foo")
|
|
||||||
.allowedMethods("GET", "POST");
|
|
||||||
registry.addMapping("/cors");
|
registry.addMapping("/cors");
|
||||||
registry.addMapping("/ambiguous")
|
registry.addMapping("/ambiguous").allowedMethods("GET", "POST");
|
||||||
.allowedMethods("GET", "POST");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -60,6 +60,10 @@ public class CorsBeanDefinitionParser implements BeanDefinitionParser {
|
|||||||
String[] allowedOrigins = StringUtils.tokenizeToStringArray(mapping.getAttribute("allowed-origins"), ",");
|
String[] allowedOrigins = StringUtils.tokenizeToStringArray(mapping.getAttribute("allowed-origins"), ",");
|
||||||
config.setAllowedOrigins(Arrays.asList(allowedOrigins));
|
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")) {
|
if (mapping.hasAttribute("allowed-methods")) {
|
||||||
String[] allowedMethods = StringUtils.tokenizeToStringArray(mapping.getAttribute("allowed-methods"), ",");
|
String[] allowedMethods = StringUtils.tokenizeToStringArray(mapping.getAttribute("allowed-methods"), ",");
|
||||||
config.setAllowedMethods(Arrays.asList(allowedMethods));
|
config.setAllowedMethods(Arrays.asList(allowedMethods));
|
||||||
@@ -78,7 +82,9 @@ public class CorsBeanDefinitionParser implements BeanDefinitionParser {
|
|||||||
if (mapping.hasAttribute("max-age")) {
|
if (mapping.hasAttribute("max-age")) {
|
||||||
config.setMaxAge(Long.parseLong(mapping.getAttribute("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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with 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;
|
package org.springframework.web.servlet.config.annotation;
|
||||||
|
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
import org.springframework.web.cors.CorsConfiguration;
|
import org.springframework.web.cors.CorsConfiguration;
|
||||||
|
|
||||||
@@ -46,24 +47,27 @@ public class CorsRegistration {
|
|||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The list of allowed origins that be specific origins, e.g.
|
* A list of origins for which cross-origin requests are allowed. Please,
|
||||||
* {@code "https://domain1.com"}, or {@code "*"} for all origins.
|
* see {@link CorsConfiguration#setAllowedOrigins(List)} for details.
|
||||||
* <p>A matched origin is listed in the {@code Access-Control-Allow-Origin}
|
* <p>By default all origins are allowed unless {@code originPatterns} is
|
||||||
* response header of preflight actual CORS requests.
|
* also set in which case {@code originPatterns} is used instead.
|
||||||
* <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.
|
|
||||||
*/
|
*/
|
||||||
public CorsRegistration allowedOrigins(String... origins) {
|
public CorsRegistration allowedOrigins(String... origins) {
|
||||||
this.config.setAllowedOrigins(Arrays.asList(origins));
|
this.config.setAllowedOrigins(Arrays.asList(origins));
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Alternative to {@link #allowCredentials} that supports origins declared
|
||||||
|
* via wildcard patterns. Please, see
|
||||||
|
* @link CorsConfiguration#setAllowedOriginPatterns(List)} for details.
|
||||||
|
* <p>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.
|
* Set the HTTP methods to allow, e.g. {@code "GET"}, {@code "POST"}, etc.
|
||||||
|
|||||||
@@ -516,6 +516,9 @@ public abstract class AbstractHandlerMapping extends WebApplicationObjectSupport
|
|||||||
CorsConfiguration globalConfig = getCorsConfigurationSource().getCorsConfiguration(request);
|
CorsConfiguration globalConfig = getCorsConfigurationSource().getCorsConfiguration(request);
|
||||||
config = (globalConfig != null ? globalConfig.combine(config) : config);
|
config = (globalConfig != null ? globalConfig.combine(config) : config);
|
||||||
}
|
}
|
||||||
|
if (config != null) {
|
||||||
|
config.validateAllowCredentials();
|
||||||
|
}
|
||||||
executionChain = getCorsHandlerExecutionChain(request, executionChain, config);
|
executionChain = getCorsHandlerExecutionChain(request, executionChain, config);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -86,7 +86,7 @@ public abstract class AbstractHandlerMethodMapping<T> extends AbstractHandlerMap
|
|||||||
private static final CorsConfiguration ALLOW_CORS_CONFIG = new CorsConfiguration();
|
private static final CorsConfiguration ALLOW_CORS_CONFIG = new CorsConfiguration();
|
||||||
|
|
||||||
static {
|
static {
|
||||||
ALLOW_CORS_CONFIG.addAllowedOrigin("*");
|
ALLOW_CORS_CONFIG.addAllowedOriginPattern("*");
|
||||||
ALLOW_CORS_CONFIG.addAllowedMethod("*");
|
ALLOW_CORS_CONFIG.addAllowedMethod("*");
|
||||||
ALLOW_CORS_CONFIG.addAllowedHeader("*");
|
ALLOW_CORS_CONFIG.addAllowedHeader("*");
|
||||||
ALLOW_CORS_CONFIG.setAllowCredentials(true);
|
ALLOW_CORS_CONFIG.setAllowCredentials(true);
|
||||||
@@ -630,9 +630,10 @@ public abstract class AbstractHandlerMethodMapping<T> extends AbstractHandlerMap
|
|||||||
addMappingName(name, handlerMethod);
|
addMappingName(name, handlerMethod);
|
||||||
}
|
}
|
||||||
|
|
||||||
CorsConfiguration corsConfig = initCorsConfiguration(handler, method, mapping);
|
CorsConfiguration config = initCorsConfiguration(handler, method, mapping);
|
||||||
if (corsConfig != null) {
|
if (config != null) {
|
||||||
this.corsLookup.put(handlerMethod, corsConfig);
|
config.validateAllowCredentials();
|
||||||
|
this.corsLookup.put(handlerMethod, config);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.registry.put(mapping, new MappingRegistration<>(mapping, handlerMethod, directUrls, name));
|
this.registry.put(mapping, new MappingRegistration<>(mapping, handlerMethod, directUrls, name));
|
||||||
|
|||||||
@@ -1346,6 +1346,15 @@
|
|||||||
Comma-separated list of origins to allow, e.g. "https://domain1.com, https://domain2.com".
|
Comma-separated list of origins to allow, e.g. "https://domain1.com, https://domain2.com".
|
||||||
The special value "*" allows all domains (default).
|
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",
|
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
|
"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
|
the client-originated address. Consider using the ForwardedHeaderFilter in order to
|
||||||
@@ -1354,6 +1363,20 @@
|
|||||||
]]></xsd:documentation>
|
]]></xsd:documentation>
|
||||||
</xsd:annotation>
|
</xsd:annotation>
|
||||||
</xsd:attribute>
|
</xsd:attribute>
|
||||||
|
<xsd:attribute name="allowed-origin-patterns" type="xsd:string">
|
||||||
|
<xsd:annotation>
|
||||||
|
<xsd:documentation><![CDATA[
|
||||||
|
Alternative to allowed-origins that supports origins declared via patterns.
|
||||||
|
In contrast to allowed-origins which does support the special value "*", this
|
||||||
|
property allows more flexible patterns, e.g. "*.domain1.com". Furthermore it
|
||||||
|
always sets the "Access-Control-Allow-Origin" response header to the matched
|
||||||
|
origin and never to "*" nor to any other pattern and therefore can be used in
|
||||||
|
combination with allowCredentials set to true.
|
||||||
|
|
||||||
|
By default this is not set.
|
||||||
|
]]></xsd:documentation>
|
||||||
|
</xsd:annotation>
|
||||||
|
</xsd:attribute>
|
||||||
<xsd:attribute name="allowed-methods" type="xsd:string">
|
<xsd:attribute name="allowed-methods" type="xsd:string">
|
||||||
<xsd:annotation>
|
<xsd:annotation>
|
||||||
<xsd:documentation><![CDATA[
|
<xsd:documentation><![CDATA[
|
||||||
|
|||||||
@@ -914,7 +914,7 @@ public class MvcNamespaceTests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testCors() throws Exception {
|
public void testCors() {
|
||||||
loadBeanDefinitions("mvc-config-cors.xml");
|
loadBeanDefinitions("mvc-config-cors.xml");
|
||||||
|
|
||||||
String[] beanNames = appContext.getBeanNamesForType(AbstractHandlerMapping.class);
|
String[] beanNames = appContext.getBeanNamesForType(AbstractHandlerMapping.class);
|
||||||
@@ -930,6 +930,7 @@ public class MvcNamespaceTests {
|
|||||||
CorsConfiguration config = configs.get("/api/**");
|
CorsConfiguration config = configs.get("/api/**");
|
||||||
assertThat(config).isNotNull();
|
assertThat(config).isNotNull();
|
||||||
assertThat(config.getAllowedOrigins().toArray()).isEqualTo(new String[]{"https://domain1.com", "https://domain2.com"});
|
assertThat(config.getAllowedOrigins().toArray()).isEqualTo(new String[]{"https://domain1.com", "https://domain2.com"});
|
||||||
|
assertThat(config.getAllowedOriginPatterns().toArray()).isEqualTo(new String[]{"http://*.domain.com"});
|
||||||
assertThat(config.getAllowedMethods().toArray()).isEqualTo(new String[]{"GET", "PUT"});
|
assertThat(config.getAllowedMethods().toArray()).isEqualTo(new String[]{"GET", "PUT"});
|
||||||
assertThat(config.getAllowedHeaders().toArray()).isEqualTo(new String[]{"header1", "header2", "header3"});
|
assertThat(config.getAllowedHeaders().toArray()).isEqualTo(new String[]{"header1", "header2", "header3"});
|
||||||
assertThat(config.getExposedHeaders().toArray()).isEqualTo(new String[]{"header1", "header2"});
|
assertThat(config.getExposedHeaders().toArray()).isEqualTo(new String[]{"header1", "header2"});
|
||||||
|
|||||||
@@ -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");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with 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;
|
package org.springframework.web.servlet.config.annotation;
|
||||||
|
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
import java.util.Collections;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
import org.junit.jupiter.api.BeforeEach;
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
@@ -61,11 +62,19 @@ public class CorsRegistryTests {
|
|||||||
assertThat(configs.size()).isEqualTo(1);
|
assertThat(configs.size()).isEqualTo(1);
|
||||||
CorsConfiguration config = configs.get("/foo");
|
CorsConfiguration config = configs.get("/foo");
|
||||||
assertThat(config.getAllowedOrigins()).isEqualTo(Arrays.asList("https://domain2.com", "https://domain2.com"));
|
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.getAllowedHeaders()).isEqualTo(Arrays.asList("header1", "header2"));
|
||||||
assertThat(config.getExposedHeaders()).isEqualTo(Arrays.asList("header3", "header4"));
|
assertThat(config.getExposedHeaders()).isEqualTo(Arrays.asList("header3", "header4"));
|
||||||
assertThat(config.getAllowCredentials()).isEqualTo(false);
|
assertThat(config.getAllowCredentials()).isEqualTo(false);
|
||||||
assertThat(config.getMaxAge()).isEqualTo(Long.valueOf(3600));
|
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("*");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -123,14 +123,14 @@ class CorsAbstractHandlerMappingTests {
|
|||||||
@PathPatternsParameterizedTest
|
@PathPatternsParameterizedTest
|
||||||
void actualRequestWithMappedPatternCorsConfiguration(TestHandlerMapping mapping) throws Exception {
|
void actualRequestWithMappedPatternCorsConfiguration(TestHandlerMapping mapping) throws Exception {
|
||||||
CorsConfiguration config = new CorsConfiguration();
|
CorsConfiguration config = new CorsConfiguration();
|
||||||
config.addAllowedOriginPattern(".*\\.domain2\\.com");
|
config.addAllowedOriginPattern("http://*.domain2.com");
|
||||||
mapping.setCorsConfigurations(Collections.singletonMap("/foo", config));
|
mapping.setCorsConfigurations(Collections.singletonMap("/foo", config));
|
||||||
MockHttpServletRequest request = getCorsRequest("/foo");
|
MockHttpServletRequest request = getCorsRequest("/foo");
|
||||||
HandlerExecutionChain chain = mapping.getHandler(request);
|
HandlerExecutionChain chain = mapping.getHandler(request);
|
||||||
|
|
||||||
assertThat(chain).isNotNull();
|
assertThat(chain).isNotNull();
|
||||||
assertThat(chain.getHandler()).isInstanceOf(SimpleHandler.class);
|
assertThat(chain.getHandler()).isInstanceOf(SimpleHandler.class);
|
||||||
assertThat(mapping.getRequiredCorsConfig().getAllowedOriginPatterns()).containsExactly(".*\\.domain2\\.com");
|
assertThat(mapping.getRequiredCorsConfig().getAllowedOriginPatterns()).containsExactly("http://*.domain2.com");
|
||||||
}
|
}
|
||||||
|
|
||||||
@PathPatternsParameterizedTest
|
@PathPatternsParameterizedTest
|
||||||
@@ -158,7 +158,8 @@ class CorsAbstractHandlerMappingTests {
|
|||||||
|
|
||||||
CorsConfiguration config = mapping.getRequiredCorsConfig();
|
CorsConfiguration config = mapping.getRequiredCorsConfig();
|
||||||
assertThat(config).isNotNull();
|
assertThat(config).isNotNull();
|
||||||
assertThat(config.getAllowedOrigins()).containsExactly("*");
|
assertThat(config.getAllowedOrigins()).isNull();
|
||||||
|
assertThat(config.getAllowedOriginPatterns()).containsExactly("*");
|
||||||
assertThat(config.getAllowCredentials()).isTrue();
|
assertThat(config.getAllowCredentials()).isTrue();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -174,7 +175,8 @@ class CorsAbstractHandlerMappingTests {
|
|||||||
|
|
||||||
CorsConfiguration config = mapping.getRequiredCorsConfig();
|
CorsConfiguration config = mapping.getRequiredCorsConfig();
|
||||||
assertThat(config).isNotNull();
|
assertThat(config).isNotNull();
|
||||||
assertThat(config.getAllowedOrigins()).containsExactly("*");
|
assertThat(config.getAllowedOrigins()).isNull();
|
||||||
|
assertThat(config.getAllowedOriginPatterns()).containsExactly("*");
|
||||||
assertThat(config.getAllowCredentials()).isTrue();
|
assertThat(config.getAllowCredentials()).isTrue();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -283,7 +285,7 @@ class CorsAbstractHandlerMappingTests {
|
|||||||
@Override
|
@Override
|
||||||
public CorsConfiguration getCorsConfiguration(HttpServletRequest request) {
|
public CorsConfiguration getCorsConfiguration(HttpServletRequest request) {
|
||||||
CorsConfiguration config = new CorsConfiguration();
|
CorsConfiguration config = new CorsConfiguration();
|
||||||
config.addAllowedOrigin("*");
|
config.addAllowedOriginPattern("*");
|
||||||
config.setAllowCredentials(true);
|
config.setAllowCredentials(true);
|
||||||
return config;
|
return config;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -55,6 +55,7 @@ import org.springframework.web.util.ServletRequestPathUtils;
|
|||||||
import org.springframework.web.util.pattern.PathPatternParser;
|
import org.springframework.web.util.pattern.PathPatternParser;
|
||||||
|
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
|
||||||
import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
|
import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -72,7 +73,7 @@ class CrossOriginTests {
|
|||||||
StaticWebApplicationContext wac = new StaticWebApplicationContext();
|
StaticWebApplicationContext wac = new StaticWebApplicationContext();
|
||||||
Properties props = new Properties();
|
Properties props = new Properties();
|
||||||
props.setProperty("myOrigin", "https://example.com");
|
props.setProperty("myOrigin", "https://example.com");
|
||||||
props.setProperty("myDomainPattern", ".*\\.example\\.com");
|
props.setProperty("myDomainPattern", "http://*.example.com");
|
||||||
wac.getEnvironment().getPropertySources().addFirst(new PropertiesPropertySource("ps", props));
|
wac.getEnvironment().getPropertySources().addFirst(new PropertiesPropertySource("ps", props));
|
||||||
wac.registerSingleton("ppc", PropertySourcesPlaceholderConfigurer.class);
|
wac.registerSingleton("ppc", PropertySourcesPlaceholderConfigurer.class);
|
||||||
wac.refresh();
|
wac.refresh();
|
||||||
@@ -206,7 +207,7 @@ class CrossOriginTests {
|
|||||||
CorsConfiguration config = getCorsConfiguration(chain, false);
|
CorsConfiguration config = getCorsConfiguration(chain, false);
|
||||||
assertThat(config).isNotNull();
|
assertThat(config).isNotNull();
|
||||||
assertThat(config.getAllowedOrigins()).isNull();
|
assertThat(config.getAllowedOrigins()).isNull();
|
||||||
assertThat(config.getAllowedOriginPatterns()).isEqualTo(Collections.singletonList(".*\\.example\\.com"));
|
assertThat(config.getAllowedOriginPatterns()).isEqualTo(Collections.singletonList("http://*.example.com"));
|
||||||
assertThat(config.getAllowCredentials()).isNull();
|
assertThat(config.getAllowCredentials()).isNull();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -218,16 +219,30 @@ class CrossOriginTests {
|
|||||||
CorsConfiguration config = getCorsConfiguration(chain, false);
|
CorsConfiguration config = getCorsConfiguration(chain, false);
|
||||||
assertThat(config).isNotNull();
|
assertThat(config).isNotNull();
|
||||||
assertThat(config.getAllowedOrigins()).isNull();
|
assertThat(config.getAllowedOrigins()).isNull();
|
||||||
assertThat(config.getAllowedOriginPatterns()).isEqualTo(Collections.singletonList(".*\\.example\\.com"));
|
assertThat(config.getAllowedOriginPatterns()).isEqualTo(Collections.singletonList("http://*.example.com"));
|
||||||
assertThat(config.getAllowCredentials()).isNull();
|
assertThat(config.getAllowCredentials()).isNull();
|
||||||
}
|
}
|
||||||
|
|
||||||
@PathPatternsParameterizedTest
|
@PathPatternsParameterizedTest
|
||||||
void bogusAllowCredentialsValue(TestRequestMappingInfoHandlerMapping mapping) {
|
void bogusAllowCredentialsValue(TestRequestMappingInfoHandlerMapping mapping) {
|
||||||
assertThatIllegalStateException().isThrownBy(() ->
|
assertThatIllegalStateException()
|
||||||
mapping.registerHandler(new MethodLevelControllerWithBogusAllowCredentialsValue()))
|
.isThrownBy(() -> mapping.registerHandler(new MethodLevelControllerWithBogusAllowCredentialsValue()))
|
||||||
.withMessageContaining("@CrossOrigin's allowCredentials")
|
.withMessageContaining("@CrossOrigin's allowCredentials")
|
||||||
.withMessageContaining("current value is [bogus]");
|
.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
|
@PathPatternsParameterizedTest
|
||||||
@@ -255,7 +270,8 @@ class CrossOriginTests {
|
|||||||
config = getCorsConfiguration(chain, false);
|
config = getCorsConfiguration(chain, false);
|
||||||
assertThat(config).isNotNull();
|
assertThat(config).isNotNull();
|
||||||
assertThat(config.getAllowedMethods()).containsExactly("GET");
|
assertThat(config.getAllowedMethods()).containsExactly("GET");
|
||||||
assertThat(config.getAllowedOrigins()).containsExactly("*");
|
assertThat(config.getAllowedOrigins()).isNull();
|
||||||
|
assertThat(config.getAllowedOriginPatterns()).containsExactly("*");
|
||||||
assertThat(config.getAllowCredentials()).isTrue();
|
assertThat(config.getAllowCredentials()).isTrue();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -313,7 +329,8 @@ class CrossOriginTests {
|
|||||||
CorsConfiguration config = getCorsConfiguration(chain, true);
|
CorsConfiguration config = getCorsConfiguration(chain, true);
|
||||||
assertThat(config).isNotNull();
|
assertThat(config).isNotNull();
|
||||||
assertThat(config.getAllowedMethods()).containsExactly("*");
|
assertThat(config.getAllowedMethods()).containsExactly("*");
|
||||||
assertThat(config.getAllowedOrigins()).containsExactly("*");
|
assertThat(config.getAllowedOrigins()).isNull();
|
||||||
|
assertThat(config.getAllowedOriginPatterns()).containsExactly("*");
|
||||||
assertThat(config.getAllowedHeaders()).containsExactly("*");
|
assertThat(config.getAllowedHeaders()).containsExactly("*");
|
||||||
assertThat(config.getAllowCredentials()).isTrue();
|
assertThat(config.getAllowCredentials()).isTrue();
|
||||||
assertThat(CollectionUtils.isEmpty(config.getExposedHeaders())).isTrue();
|
assertThat(CollectionUtils.isEmpty(config.getExposedHeaders())).isTrue();
|
||||||
@@ -330,7 +347,8 @@ class CrossOriginTests {
|
|||||||
CorsConfiguration config = getCorsConfiguration(chain, true);
|
CorsConfiguration config = getCorsConfiguration(chain, true);
|
||||||
assertThat(config).isNotNull();
|
assertThat(config).isNotNull();
|
||||||
assertThat(config.getAllowedMethods()).containsExactly("*");
|
assertThat(config.getAllowedMethods()).containsExactly("*");
|
||||||
assertThat(config.getAllowedOrigins()).containsExactly("*");
|
assertThat(config.getAllowedOrigins()).isNull();
|
||||||
|
assertThat(config.getAllowedOriginPatterns()).containsExactly("*");
|
||||||
assertThat(config.getAllowedHeaders()).containsExactly("*");
|
assertThat(config.getAllowedHeaders()).containsExactly("*");
|
||||||
assertThat(config.getAllowCredentials()).isTrue();
|
assertThat(config.getAllowCredentials()).isTrue();
|
||||||
assertThat(CollectionUtils.isEmpty(config.getExposedHeaders())).isTrue();
|
assertThat(CollectionUtils.isEmpty(config.getExposedHeaders())).isTrue();
|
||||||
@@ -433,7 +451,7 @@ class CrossOriginTests {
|
|||||||
public void customOriginDefinedViaPlaceholder() {
|
public void customOriginDefinedViaPlaceholder() {
|
||||||
}
|
}
|
||||||
|
|
||||||
@CrossOrigin(originPatterns = ".*\\.example\\.com")
|
@CrossOrigin(originPatterns = "http://*.example.com")
|
||||||
@RequestMapping("/customOriginPattern")
|
@RequestMapping("/customOriginPattern")
|
||||||
public void customOriginPatternDefinedViaValueAttribute() {
|
public void customOriginPatternDefinedViaValueAttribute() {
|
||||||
}
|
}
|
||||||
@@ -469,11 +487,31 @@ class CrossOriginTests {
|
|||||||
public void bar() {
|
public void bar() {
|
||||||
}
|
}
|
||||||
|
|
||||||
@CrossOrigin(allowCredentials = "true")
|
@CrossOrigin(originPatterns = "*", allowCredentials = "true")
|
||||||
@RequestMapping(path = "/baz", method = RequestMethod.GET)
|
@RequestMapping(path = "/baz", method = RequestMethod.GET)
|
||||||
public void baz() {
|
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)
|
@RequestMapping(path = "/foo", method = RequestMethod.GET)
|
||||||
public void foo() {
|
public void foo() {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -10,6 +10,7 @@
|
|||||||
<mvc:cors>
|
<mvc:cors>
|
||||||
|
|
||||||
<mvc:mapping path="/api/**" allowed-origins="https://domain1.com, https://domain2.com"
|
<mvc:mapping path="/api/**" allowed-origins="https://domain1.com, https://domain2.com"
|
||||||
|
allowed-origin-patterns="http://*.domain.com"
|
||||||
allowed-methods="GET, PUT" allowed-headers="header1, header2, header3"
|
allowed-methods="GET, PUT" allowed-headers="header1, header2, header3"
|
||||||
exposed-headers="header1, header2" allow-credentials="false" max-age="123" />
|
exposed-headers="header1, header2" allow-credentials="false" max-age="123" />
|
||||||
|
|
||||||
|
|||||||
@@ -128,10 +128,11 @@ By default, `@CrossOrigin` allows:
|
|||||||
* All headers.
|
* All headers.
|
||||||
* All HTTP methods to which the controller method is mapped.
|
* All HTTP methods to which the controller method is mapped.
|
||||||
|
|
||||||
|
`allowCredentials` is not enabled by default, since that establishes a trust level
|
||||||
`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
|
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.
|
`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
|
`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
|
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.
|
`maxAge` is set to 30 minutes.
|
||||||
|
|
||||||
|
|||||||
@@ -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,
|
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
|
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)`]
|
{api-spring-framework}/web/cors/CorsConfiguration.html#combine-org.springframework.web.cors.CorsConfiguration-[`CorsConfiguration#combine(CorsConfiguration)`]
|
||||||
for more details.
|
for more details.
|
||||||
|
|
||||||
@@ -128,9 +128,11 @@ By default, `@CrossOrigin` allows:
|
|||||||
* All headers.
|
* All headers.
|
||||||
* All HTTP methods to which the controller method is mapped.
|
* 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
|
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.
|
`maxAge` is set to 30 minutes.
|
||||||
|
|
||||||
@@ -238,9 +240,11 @@ By default, global configuration enables the following:
|
|||||||
* `GET`, `HEAD`, and `POST` methods.
|
* `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
|
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.
|
`maxAge` is set to 30 minutes.
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user