Support coma-delimited format for origin values
Closes gh-27606
This commit is contained in:
parent
d516667f37
commit
badba7c3e9
|
|
@ -21,11 +21,10 @@ import java.util.ArrayList;
|
|||
import java.util.Collections;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
|
@ -120,9 +119,16 @@ public class CorsConfiguration {
|
|||
|
||||
|
||||
/**
|
||||
* A list of origins for which cross-origin requests are allowed. Values may
|
||||
* be a specific domain, e.g. {@code "https://domain1.com"}, or the CORS
|
||||
* defined special value {@code "*"} for all origins.
|
||||
* A list of origins for which cross-origin requests are allowed where each
|
||||
* value may be one of the following:
|
||||
* <ul>
|
||||
* <li>a specific domain, e.g. {@code "https://domain1.com"}
|
||||
* <li>coma-delimited list of specific domains, e.g.
|
||||
* {@code "https://a1.com,https://a2.com"}; this is convenient when a value
|
||||
* is resolved through a property placeholder, e.g. {@code "${origin}"};
|
||||
* note that such placeholders must be resolved externally.
|
||||
* <li>the CORS defined special value {@code "*"} for all origins
|
||||
* </ul>
|
||||
* <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
|
||||
|
|
@ -135,8 +141,15 @@ public class CorsConfiguration {
|
|||
* {@code @CrossOrigin}, via {@link #applyPermitDefaultValues()}.
|
||||
*/
|
||||
public void setAllowedOrigins(@Nullable List<String> origins) {
|
||||
this.allowedOrigins = (origins == null ? null :
|
||||
origins.stream().filter(Objects::nonNull).map(this::trimTrailingSlash).collect(Collectors.toList()));
|
||||
if (origins == null) {
|
||||
this.allowedOrigins = null;
|
||||
}
|
||||
else {
|
||||
this.allowedOrigins = new ArrayList<>(origins.size());
|
||||
for (String origin : origins) {
|
||||
addAllowedOrigin(origin);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private String trimTrailingSlash(String origin) {
|
||||
|
|
@ -164,8 +177,10 @@ public class CorsConfiguration {
|
|||
else if (this.allowedOrigins == DEFAULT_PERMIT_ALL && CollectionUtils.isEmpty(this.allowedOriginPatterns)) {
|
||||
setAllowedOrigins(DEFAULT_PERMIT_ALL);
|
||||
}
|
||||
origin = trimTrailingSlash(origin);
|
||||
this.allowedOrigins.add(origin);
|
||||
parseComaDelimitedOrigin(origin, value -> {
|
||||
value = trimTrailingSlash(value);
|
||||
this.allowedOrigins.add(value);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -178,12 +193,16 @@ public class CorsConfiguration {
|
|||
* domain1.com on port 8080 or port 8081
|
||||
* <li>{@literal https://*.domain1.com:[*]} -- domains ending with
|
||||
* domain1.com on any port, including the default port
|
||||
* <li>coma-delimited list of patters, e.g.
|
||||
* {@code "https://*.a1.com,https://*.a2.com"}; this is convenient when a
|
||||
* value is resolved through a property placeholder, e.g. {@code "${origin}"};
|
||||
* note that such placeholders must be resolved externally.
|
||||
* </ul>
|
||||
* <p>In contrast to {@link #setAllowedOrigins(List) allowedOrigins} which
|
||||
* only supports "*" and cannot be used with {@code allowCredentials}, when
|
||||
* an allowedOriginPattern is matched, the {@code Access-Control-Allow-Origin}
|
||||
* response header is set to the matched origin and not to {@code "*"} nor
|
||||
* to the pattern. Therefore allowedOriginPatterns can be used in combination
|
||||
* to the pattern. Therefore, allowedOriginPatterns can be used in combination
|
||||
* with {@link #setAllowCredentials} set to {@code true}.
|
||||
* <p>By default this is not set.
|
||||
* @since 5.3
|
||||
|
|
@ -226,11 +245,41 @@ public class CorsConfiguration {
|
|||
if (this.allowedOriginPatterns == null) {
|
||||
this.allowedOriginPatterns = new ArrayList<>(4);
|
||||
}
|
||||
originPattern = trimTrailingSlash(originPattern);
|
||||
this.allowedOriginPatterns.add(new OriginPattern(originPattern));
|
||||
parseComaDelimitedOrigin(originPattern, value -> {
|
||||
value = trimTrailingSlash(value);
|
||||
this.allowedOriginPatterns.add(new OriginPattern(value));
|
||||
if (this.allowedOrigins == DEFAULT_PERMIT_ALL) {
|
||||
this.allowedOrigins = null;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private static void parseComaDelimitedOrigin(String rawValue, Consumer<String> valueConsumer) {
|
||||
if (rawValue.indexOf(',') == -1) {
|
||||
valueConsumer.accept(rawValue);
|
||||
return;
|
||||
}
|
||||
int start = 0;
|
||||
boolean withinPortRange = false;
|
||||
for (int current = 0; current < rawValue.length(); current++) {
|
||||
switch (rawValue.charAt(current)) {
|
||||
case '[':
|
||||
withinPortRange = true;
|
||||
break;
|
||||
case ']':
|
||||
withinPortRange = false;
|
||||
break;
|
||||
case ',':
|
||||
if (!withinPortRange) {
|
||||
valueConsumer.accept(rawValue.substring(start, current).trim());
|
||||
start = current + 1;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (start < rawValue.length()) {
|
||||
valueConsumer.accept(rawValue.substring(start));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2002-2021 the original author or authors.
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
|
@ -291,6 +291,11 @@ class CorsConfigurationTests {
|
|||
config.setAllowCredentials(true);
|
||||
assertThatIllegalArgumentException().isThrownBy(() -> config.checkOrigin("https://domain.com"));
|
||||
|
||||
// coma-delimited origins list
|
||||
config.setAllowedOrigins(Collections.singletonList("https://a1.com,https://a2.com"));
|
||||
assertThat(config.checkOrigin("https://a1.com")).isEqualTo("https://a1.com");
|
||||
assertThat(config.checkOrigin("https://a2.com/")).isEqualTo("https://a2.com/");
|
||||
|
||||
// specific origin matches Origin header with or without trailing "/"
|
||||
config.setAllowedOrigins(Collections.singletonList("https://domain.com"));
|
||||
assertThat(config.checkOrigin("https://domain.com")).isEqualTo("https://domain.com");
|
||||
|
|
@ -344,6 +349,12 @@ class CorsConfigurationTests {
|
|||
assertThat(config.checkOrigin("https://example.specific.port.com:8081")).isEqualTo("https://example.specific.port.com:8081");
|
||||
assertThat(config.checkOrigin("https://example.specific.port.com:1234")).isNull();
|
||||
|
||||
config.addAllowedOriginPattern("https://*.a1.com:[8080,8081],https://*.a2.com");
|
||||
assertThat(config.checkOrigin("https://example.a1.com:8080")).isEqualTo("https://example.a1.com:8080");
|
||||
assertThat(config.checkOrigin("https://example.a1.com:8081")).isEqualTo("https://example.a1.com:8081");
|
||||
assertThat(config.checkOrigin("https://example.a1.com:8082")).isNull();
|
||||
assertThat(config.checkOrigin("https://example.a2.com")).isEqualTo("https://example.a2.com");
|
||||
|
||||
config.setAllowCredentials(false);
|
||||
assertThat(config.checkOrigin("https://example.domain.com")).isEqualTo("https://example.domain.com");
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue