diff --git a/spring-web/src/main/java/org/springframework/web/bind/annotation/CrossOrigin.java b/spring-web/src/main/java/org/springframework/web/bind/annotation/CrossOrigin.java index 8e2de23b5d4..ca755049783 100644 --- a/spring-web/src/main/java/org/springframework/web/bind/annotation/CrossOrigin.java +++ b/spring-web/src/main/java/org/springframework/web/bind/annotation/CrossOrigin.java @@ -21,6 +21,7 @@ import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import java.util.List; import org.springframework.core.annotation.AliasFor; import org.springframework.web.cors.CorsConfiguration; @@ -77,37 +78,20 @@ public @interface CrossOrigin { String[] value() default {}; /** - * The list of allowed origins that be specific origins, e.g. - * {@code "https://domain1.com"}, or {@code "*"} for all origins. - *

A matched origin is listed in the {@code Access-Control-Allow-Origin} - * response header of preflight actual CORS requests. - *

By default all origins are allowed. - *

Note: CORS checks use values from "Forwarded" - * (RFC 7239), - * "X-Forwarded-Host", "X-Forwarded-Port", and "X-Forwarded-Proto" headers, - * if present, in order to reflect the client-originated address. - * Consider using the {@code ForwardedHeaderFilter} in order to choose from a - * central place whether to extract and use, or to discard such headers. - * See the Spring Framework reference for more on this filter. - * @see #value + * A list of origins for which cross-origin requests are allowed. Please, + * see {@link CorsConfiguration#setAllowedOrigins(List)} for details. + *

By default all origins are allowed unless {@code originPatterns} is + * also set in which case {@code originPatterns} is used instead. */ @AliasFor("value") String[] origins() default {}; /** - * The list of allowed origins patterns that be specific origins, e.g. - * {@code ".*\.domain1\.com"}, or {@code ".*"} for matching all origins. - *

A matched origin is listed in the {@code Access-Control-Allow-Origin} - * response header of preflight actual CORS requests. - *

By default all origins are allowed. - *

Note: CORS checks use values from "Forwarded" - * (RFC 7239), - * "X-Forwarded-Host", "X-Forwarded-Port", and "X-Forwarded-Proto" headers, - * if present, in order to reflect the client-originated address. - * Consider using the {@code ForwardedHeaderFilter} in order to choose from a - * central place whether to extract and use, or to discard such headers. - * See the Spring Framework reference for more on this filter. - * @see #value + * Alternative to {@link #origins()} that supports origins declared via + * wildcard patterns. Please, see + * @link CorsConfiguration#setAllowedOriginPatterns(List)} for details. + *

By default this is not set. + * @since 5.3 */ String[] originPatterns() default {}; diff --git a/spring-web/src/main/java/org/springframework/web/cors/CorsConfiguration.java b/spring-web/src/main/java/org/springframework/web/cors/CorsConfiguration.java index db63d61fa5a..6ca27ac9bab 100644 --- a/spring-web/src/main/java/org/springframework/web/cors/CorsConfiguration.java +++ b/spring-web/src/main/java/org/springframework/web/cors/CorsConfiguration.java @@ -54,30 +54,30 @@ public class CorsConfiguration { /** Wildcard representing all origins, methods, or headers. */ public static final String ALL = "*"; - /** Wildcard representing pattern that matches all origins. */ - public static final String ALL_PATTERN = ".*"; - private static final List DEFAULT_METHODS = Collections.unmodifiableList( - Arrays.asList(HttpMethod.GET, HttpMethod.HEAD)); + private static final List ALL_LIST = Collections.unmodifiableList( + Collections.singletonList(ALL)); - private static final List DEFAULT_PERMIT_METHODS = Collections.unmodifiableList( - Arrays.asList(HttpMethod.GET.name(), HttpMethod.HEAD.name(), HttpMethod.POST.name())); + private static final OriginPattern ALL_PATTERN = new OriginPattern("*"); + + private static final List ALL_PATTERN_LIST = Collections.unmodifiableList( + Collections.singletonList(ALL_PATTERN)); private static final List DEFAULT_PERMIT_ALL = Collections.unmodifiableList( Collections.singletonList(ALL)); - private static final List DEFAULT_PERMIT_ALL_PATTERN_STR = Collections.unmodifiableList( - Collections.singletonList(ALL_PATTERN)); + private static final List DEFAULT_METHODS = Collections.unmodifiableList( + Arrays.asList(HttpMethod.GET, HttpMethod.HEAD)); - private static final List DEFAULT_PERMIT_ALL_PATTERN = Collections.unmodifiableList( - Collections.singletonList(Pattern.compile(ALL_PATTERN))); + private static final List DEFAULT_PERMIT_METHODS = Collections.unmodifiableList( + Arrays.asList(HttpMethod.GET.name(), HttpMethod.HEAD.name(), HttpMethod.POST.name())); @Nullable private List allowedOrigins; @Nullable - private List allowedOriginPatterns; + private List allowedOriginPatterns; @Nullable private List allowedMethods; @@ -123,9 +123,19 @@ public class CorsConfiguration { /** - * Set the origins to allow, e.g. {@code "https://domain1.com"}. - *

The special value {@code "*"} allows all domains. - *

By default this is not set. + * A list of origins for which cross-origin requests are allowed. Values may + * be a specific domain, e.g. {@code "https://domain1.com"}, or the CORS + * defined special value {@code "*"} for all origins. + *

For matched pre-flight and actual requests the + * {@code Access-Control-Allow-Origin} response header is set either to the + * matched domain value or to {@code "*"}. Keep in mind however that the + * CORS spec does not allow {@code "*"} when {@link #setAllowCredentials + * allowCredentials} is set to {@code true} and as of 5.3 that combination + * is rejected in favor of using {@link #setAllowedOriginPatterns + * allowedOriginPatterns} instead. + *

By default this is not set which means that no origins are allowed. + * However an instance of this class is often initialized further, e.g. for + * {@code @CrossOrigin}, via {@link #applyPermitDefaultValues()}. */ public void setAllowedOrigins(@Nullable List allowedOrigins) { this.allowedOrigins = (allowedOrigins != null ? new ArrayList<>(allowedOrigins) : null); @@ -133,8 +143,6 @@ public class CorsConfiguration { /** * Return the configured origins to allow, or {@code null} if none. - * @see #addAllowedOrigin(String) - * @see #setAllowedOrigins(List) */ @Nullable public List getAllowedOrigins() { @@ -142,21 +150,29 @@ public class CorsConfiguration { } /** - * Add an origin to allow. + * Variant of {@link #setAllowedOrigins} for adding one origin at a time. */ public void addAllowedOrigin(String origin) { if (this.allowedOrigins == null) { this.allowedOrigins = new ArrayList<>(4); } - else if (this.allowedOrigins == DEFAULT_PERMIT_ALL) { + else if (this.allowedOrigins == DEFAULT_PERMIT_ALL && CollectionUtils.isEmpty(this.allowedOriginPatterns)) { setAllowedOrigins(DEFAULT_PERMIT_ALL); } this.allowedOrigins.add(origin); } /** - * Set the origins patterns to allow, e.g. {@code "*.com"}. + * Alternative to {@link #setAllowedOrigins} that supports origins declared + * via wildcard patterns. In contrast to {@link #setAllowedOrigins allowedOrigins} + * which does support the special value {@code "*"}, this property allows + * more flexible patterns, e.g. {@code "https://*.domain1.com"}. Furthermore + * it always sets the {@code Access-Control-Allow-Origin} response header to + * the matched origin and never to {@code "*"}, nor to any other pattern, and + * therefore can be used in combination with {@link #setAllowCredentials} + * set to {@code true}. *

By default this is not set. + * @since 5.3 */ public CorsConfiguration setAllowedOriginPatterns(@Nullable List allowedOriginPatterns) { if (allowedOriginPatterns == null) { @@ -164,42 +180,39 @@ public class CorsConfiguration { } else { this.allowedOriginPatterns = new ArrayList<>(allowedOriginPatterns.size()); - for (String pattern : allowedOriginPatterns) { - this.allowedOriginPatterns.add(Pattern.compile(pattern)); + for (String patternValue : allowedOriginPatterns) { + addAllowedOriginPattern(patternValue); } } - return this; } /** * Return the configured origins patterns to allow, or {@code null} if none. - * - * @see #addAllowedOriginPattern(String) - * @see #setAllowedOriginPatterns(List) + * @since 5.3 */ @Nullable public List getAllowedOriginPatterns() { if (this.allowedOriginPatterns == null) { return null; } - if (this.allowedOriginPatterns == DEFAULT_PERMIT_ALL_PATTERN) { - return DEFAULT_PERMIT_ALL_PATTERN_STR; - } - return this.allowedOriginPatterns.stream().map(Pattern::toString).collect(Collectors.toList()); + return this.allowedOriginPatterns.stream() + .map(OriginPattern::getDeclaredPattern) + .collect(Collectors.toList()); } /** - * Add an origin pattern to allow. + * Variant of {@link #setAllowedOriginPatterns} for adding one origin at a time. + * @since 5.3 */ public void addAllowedOriginPattern(String originPattern) { if (this.allowedOriginPatterns == null) { this.allowedOriginPatterns = new ArrayList<>(4); } - else if (this.allowedOriginPatterns == DEFAULT_PERMIT_ALL_PATTERN) { - setAllowedOriginPatterns(DEFAULT_PERMIT_ALL_PATTERN_STR); + this.allowedOriginPatterns.add(new OriginPattern(originPattern)); + if (this.allowedOrigins == DEFAULT_PERMIT_ALL) { + this.allowedOrigins = null; } - this.allowedOriginPatterns.add(Pattern.compile(originPattern)); } /** @@ -397,16 +410,15 @@ public class CorsConfiguration { /** - * By default a newly created {@code CorsConfiguration} does not permit any - * cross-origin requests and must be configured explicitly to indicate what - * should be allowed. - *

Use this method to flip the initialization model to start with open - * defaults that permit all cross-origin requests for GET, HEAD, and POST - * requests. Note however that this method will not override any existing - * values already set. - *

The following defaults are applied if not already set: + * By default {@code CorsConfiguration} does not permit any cross-origin + * requests and must be configured explicitly. Use this method to switch to + * defaults that permit all cross-origin requests for GET, HEAD, and POST, + * but not overriding any values that have already been set. + *

The following defaults are applied for values that are not set: *