diff --git a/spring-web-reactive/src/main/java/org/springframework/web/reactive/config/CorsRegistration.java b/spring-web-reactive/src/main/java/org/springframework/web/reactive/config/CorsRegistration.java index 94a745e970..33bfca0b61 100644 --- a/spring-web-reactive/src/main/java/org/springframework/web/reactive/config/CorsRegistration.java +++ b/spring-web-reactive/src/main/java/org/springframework/web/reactive/config/CorsRegistration.java @@ -19,20 +19,16 @@ package org.springframework.web.reactive.config; import java.util.ArrayList; import java.util.Arrays; -import org.springframework.http.HttpMethod; -import org.springframework.web.bind.annotation.CrossOrigin; import org.springframework.web.cors.CorsConfiguration; /** * Assists with the creation of a {@link CorsConfiguration} instance mapped to - * a path pattern. - * - *
If no path pattern is specified, by default cross-origin request handling - * is mapped to {@code "/**"}. Also by default, all origins, headers, - * credentials and {@code GET}, {@code HEAD}, and {@code POST} methods are - * allowed, while the max age is set to 30 minutes. + * a path pattern. By default all origins, headers, and credentials for + * {@code GET}, {@code HEAD}, and {@code POST} requests are allowed while the + * max age is set to 30 minutes. * * @author Sebastien Deleuze + * @author Rossen Stoyanchev * @since 5.0 * @see CorsRegistry */ @@ -43,44 +39,84 @@ public class CorsRegistration { private final CorsConfiguration config; + /** + * Create a new {@link CorsRegistration} that allows all origins, headers, and + * credentials for {@code GET}, {@code HEAD}, and {@code POST} requests with + * max age set to 1800 seconds (30 minutes) for the specified path. + * + * @param pathPattern the path that the CORS configuration should apply to; + * exact path mapping URIs (such as {@code "/admin"}) are supported as well + * as Ant-style path patterns (such as {@code "/admin/**"}). + */ public CorsRegistration(String pathPattern) { this.pathPattern = pathPattern; - // Same implicit default values as the @CrossOrigin annotation + allows simple methods - this.config = new CorsConfiguration(); - this.config.setAllowedOrigins(Arrays.asList(CrossOrigin.DEFAULT_ORIGINS)); - this.config.setAllowedMethods(Arrays.asList(HttpMethod.GET.name(), - HttpMethod.HEAD.name(), HttpMethod.POST.name())); - this.config.setAllowedHeaders(Arrays.asList(CrossOrigin.DEFAULT_ALLOWED_HEADERS)); - this.config.setAllowCredentials(CrossOrigin.DEFAULT_ALLOW_CREDENTIALS); - this.config.setMaxAge(CrossOrigin.DEFAULT_MAX_AGE); + this.config = new CorsConfiguration().applyPermitDefaultValues(); } + /** + * Set the origins to allow, e.g. {@code "http://domain1.com"}. + *
The special value {@code "*"} allows all domains. + *
By default all origins are allowed. + */ public CorsRegistration allowedOrigins(String... origins) { this.config.setAllowedOrigins(new ArrayList<>(Arrays.asList(origins))); return this; } + /** + * Set the HTTP methods to allow, e.g. {@code "GET"}, {@code "POST"}, etc. + *
The special value {@code "*"} allows all methods. + *
By default "simple" methods {@code GET}, {@code HEAD}, and {@code POST} + * are allowed. + */ public CorsRegistration allowedMethods(String... methods) { this.config.setAllowedMethods(new ArrayList<>(Arrays.asList(methods))); return this; } + /** + * Set the list of headers that a pre-flight request can list as allowed + * for use during an actual request. + *
The special value {@code "*"} may be used to allow all headers. + *
A header name is not required to be listed if it is one of: + * {@code Cache-Control}, {@code Content-Language}, {@code Expires}, + * {@code Last-Modified}, or {@code Pragma} as per the CORS spec. + *
By default all headers are allowed. + */ public CorsRegistration allowedHeaders(String... headers) { this.config.setAllowedHeaders(new ArrayList<>(Arrays.asList(headers))); return this; } + /** + * Set the list of response headers other than "simple" headers, i.e. + * {@code Cache-Control}, {@code Content-Language}, {@code Content-Type}, + * {@code Expires}, {@code Last-Modified}, or {@code Pragma}, that an + * actual response might have and can be exposed. + *
Note that {@code "*"} is not supported on this property. + *
By default this is not set. + */ public CorsRegistration exposedHeaders(String... headers) { this.config.setExposedHeaders(new ArrayList<>(Arrays.asList(headers))); return this; } + /** + * Configure how long in seconds the response from a pre-flight request + * can be cached by clients. + *
By default this is set to 1800 seconds (30 minutes). + */ public CorsRegistration maxAge(long maxAge) { this.config.setMaxAge(maxAge); return this; } + /** + * Whether user credentials are supported. + *
By default this is set to {@code true} in which case user credentials + * are supported. + */ public CorsRegistration allowCredentials(boolean allowCredentials) { this.config.setAllowCredentials(allowCredentials); return this; diff --git a/spring-web-reactive/src/main/java/org/springframework/web/reactive/result/method/annotation/RequestMappingHandlerMapping.java b/spring-web-reactive/src/main/java/org/springframework/web/reactive/result/method/annotation/RequestMappingHandlerMapping.java index 439a5c78b9..547fadd451 100644 --- a/spring-web-reactive/src/main/java/org/springframework/web/reactive/result/method/annotation/RequestMappingHandlerMapping.java +++ b/spring-web-reactive/src/main/java/org/springframework/web/reactive/result/method/annotation/RequestMappingHandlerMapping.java @@ -18,7 +18,6 @@ package org.springframework.web.reactive.result.method.annotation; import java.lang.reflect.AnnotatedElement; import java.lang.reflect.Method; -import java.util.Arrays; import java.util.Set; import org.springframework.context.EmbeddedValueResolverAware; @@ -294,25 +293,12 @@ public class RequestMappingHandlerMapping extends RequestMappingInfoHandlerMappi updateCorsConfig(config, typeAnnotation); updateCorsConfig(config, methodAnnotation); - if (CollectionUtils.isEmpty(config.getAllowedOrigins())) { - config.setAllowedOrigins(Arrays.asList(CrossOrigin.DEFAULT_ORIGINS)); - } if (CollectionUtils.isEmpty(config.getAllowedMethods())) { for (RequestMethod allowedMethod : mappingInfo.getMethodsCondition().getMethods()) { config.addAllowedMethod(allowedMethod.name()); } } - if (CollectionUtils.isEmpty(config.getAllowedHeaders())) { - config.setAllowedHeaders(Arrays.asList(CrossOrigin.DEFAULT_ALLOWED_HEADERS)); - } - if (config.getAllowCredentials() == null) { - config.setAllowCredentials(CrossOrigin.DEFAULT_ALLOW_CREDENTIALS); - } - if (config.getMaxAge() == null) { - config.setMaxAge(CrossOrigin.DEFAULT_MAX_AGE); - } - - return config; + return config.applyPermitDefaultValues(); } private void updateCorsConfig(CorsConfiguration config, CrossOrigin annotation) { 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 a859be9efa..34a061cd0d 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 @@ -23,11 +23,15 @@ import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import org.springframework.core.annotation.AliasFor; +import org.springframework.web.cors.CorsConfiguration; /** * Marks the annotated method or type as permitting cross origin requests. * - *
By default, all origins and headers are permitted. + *
By default all origins and headers are permitted, credentials are allowed, + * and the maximum age is set to 1800 seconds (30 minutes). The list of HTTP + * methods is set to the methods on the {@code @RequestMapping} if not + * explicitly set on {@code @CrossOrigin}. * *
NOTE: {@code @CrossOrigin} is processed if an appropriate * {@code HandlerMapping}-{@code HandlerAdapter} pair is configured such as the @@ -44,12 +48,28 @@ import org.springframework.core.annotation.AliasFor; @Documented public @interface CrossOrigin { + /** + * @deprecated as of Spring 5.0, in favor of using {@link CorsConfiguration#applyPermitDefaultValues} + */ + @Deprecated String[] DEFAULT_ORIGINS = { "*" }; + /** + * @deprecated as of Spring 5.0, in favor of using {@link CorsConfiguration#applyPermitDefaultValues} + */ + @Deprecated String[] DEFAULT_ALLOWED_HEADERS = { "*" }; + /** + * @deprecated as of Spring 5.0, in favor of using {@link CorsConfiguration#applyPermitDefaultValues} + */ + @Deprecated boolean DEFAULT_ALLOW_CREDENTIALS = true; + /** + * @deprecated as of Spring 5.0, in favor of using {@link CorsConfiguration#applyPermitDefaultValues} + */ + @Deprecated long DEFAULT_MAX_AGE = 1800; 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 a5b9f3cbb3..cdebc9db2a 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 @@ -17,6 +17,7 @@ package org.springframework.web.cors; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collections; import java.util.LinkedHashSet; import java.util.List; @@ -28,8 +29,16 @@ import org.springframework.util.ObjectUtils; import org.springframework.util.StringUtils; /** - * A container for CORS configuration that also provides methods to check - * the actual or requested origin, HTTP methods, and headers. + * A container for CORS configuration along with methods to check against the + * actual origin, HTTP methods, and headers of a given request. + * + *
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 {@link #applyPermitDefaultValues()} to flip the initialization model + * to start with open defaults that permit all cross-origin requests for GET, + * HEAD, and POST requests. * * @author Sebastien Deleuze * @author Rossen Stoyanchev @@ -71,7 +80,9 @@ public class CorsConfiguration { /** - * Construct a new, empty {@code CorsConfiguration} instance. + * Construct a new {@code CorsConfiguration} instance with no cross-origin + * requests allowed for any origin by default. + * @see #applyPermitDefaultValues() */ public CorsConfiguration() { } @@ -91,45 +102,6 @@ public class CorsConfiguration { } - /** - * Combine the supplied {@code CorsConfiguration} with this one. - *
Properties of this configuration are overridden by any non-null
- * properties of the supplied one.
- * @return the combined {@code CorsConfiguration} or {@code this}
- * configuration if the supplied configuration is {@code null}
- */
- public CorsConfiguration combine(CorsConfiguration other) {
- if (other == null) {
- return this;
- }
- CorsConfiguration config = new CorsConfiguration(this);
- config.setAllowedOrigins(combine(getAllowedOrigins(), other.getAllowedOrigins()));
- config.setAllowedMethods(combine(getAllowedMethods(), other.getAllowedMethods()));
- config.setAllowedHeaders(combine(getAllowedHeaders(), other.getAllowedHeaders()));
- config.setExposedHeaders(combine(getExposedHeaders(), other.getExposedHeaders()));
- Boolean allowCredentials = other.getAllowCredentials();
- if (allowCredentials != null) {
- config.setAllowCredentials(allowCredentials);
- }
- Long maxAge = other.getMaxAge();
- if (maxAge != null) {
- config.setMaxAge(maxAge);
- }
- return config;
- }
-
- private List The special value {@code "*"} allows all domains.
@@ -325,6 +297,83 @@ public class CorsConfiguration {
return this.maxAge;
}
+ /**
+ * 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:
+ * Properties of this configuration are overridden by any non-null
+ * properties of the supplied one.
+ * @return the combined {@code CorsConfiguration} or {@code this}
+ * configuration if the supplied configuration is {@code null}
+ */
+ public CorsConfiguration combine(CorsConfiguration other) {
+ if (other == null) {
+ return this;
+ }
+ CorsConfiguration config = new CorsConfiguration(this);
+ config.setAllowedOrigins(combine(getAllowedOrigins(), other.getAllowedOrigins()));
+ config.setAllowedMethods(combine(getAllowedMethods(), other.getAllowedMethods()));
+ config.setAllowedHeaders(combine(getAllowedHeaders(), other.getAllowedHeaders()));
+ config.setExposedHeaders(combine(getExposedHeaders(), other.getExposedHeaders()));
+ Boolean allowCredentials = other.getAllowCredentials();
+ if (allowCredentials != null) {
+ config.setAllowCredentials(allowCredentials);
+ }
+ Long maxAge = other.getMaxAge();
+ if (maxAge != null) {
+ config.setMaxAge(maxAge);
+ }
+ return config;
+ }
+
+ private List If no path pattern is specified, cross-origin request handling is
- * mapped to {@code "/**"}.
- *
- * By default, all origins, all headers, credentials and {@code GET},
- * {@code HEAD}, and {@code POST} methods are allowed, and the max age is
- * set to 30 minutes.
+ * Assists with the creation of a {@link CorsConfiguration} instance mapped to
+ * a path pattern. By default all origins, headers, and credentials for
+ * {@code GET}, {@code HEAD}, and {@code POST} requests are allowed while the
+ * max age is set to 30 minutes.
*
* @author Sebastien Deleuze
+ * @author Rossen Stoyanchev
* @author Sam Brannen
* @since 4.2
* @see CorsConfiguration
@@ -47,43 +41,85 @@ public class CorsRegistration {
private final CorsConfiguration config;
+ /**
+ * Create a new {@link CorsRegistration} that allows all origins, headers, and
+ * credentials for {@code GET}, {@code HEAD}, and {@code POST} requests with
+ * max age set to 1800 seconds (30 minutes) for the specified path.
+ *
+ * @param pathPattern the path that the CORS configuration should apply to;
+ * exact path mapping URIs (such as {@code "/admin"}) are supported as well
+ * as Ant-style path patterns (such as {@code "/admin/**"}).
+ */
public CorsRegistration(String pathPattern) {
this.pathPattern = pathPattern;
// Same implicit default values as the @CrossOrigin annotation + allows simple methods
- this.config = new CorsConfiguration();
- this.config.setAllowedOrigins(Arrays.asList(CrossOrigin.DEFAULT_ORIGINS));
- this.config.setAllowedMethods(Arrays.asList(HttpMethod.GET.name(),
- HttpMethod.HEAD.name(), HttpMethod.POST.name()));
- this.config.setAllowedHeaders(Arrays.asList(CrossOrigin.DEFAULT_ALLOWED_HEADERS));
- this.config.setAllowCredentials(CrossOrigin.DEFAULT_ALLOW_CREDENTIALS);
- this.config.setMaxAge(CrossOrigin.DEFAULT_MAX_AGE);
+ this.config = new CorsConfiguration().applyPermitDefaultValues();
}
+ /**
+ * Set the origins to allow, e.g. {@code "http://domain1.com"}.
+ * The special value {@code "*"} allows all domains.
+ * By default, all origins are allowed.
+ */
public CorsRegistration allowedOrigins(String... origins) {
this.config.setAllowedOrigins(new ArrayList<>(Arrays.asList(origins)));
return this;
}
+
+ /**
+ * Set the HTTP methods to allow, e.g. {@code "GET"}, {@code "POST"}, etc.
+ * The special value {@code "*"} allows all methods.
+ * By default "simple" methods {@code GET}, {@code HEAD}, and {@code POST}
+ * are allowed.
+ */
public CorsRegistration allowedMethods(String... methods) {
this.config.setAllowedMethods(new ArrayList<>(Arrays.asList(methods)));
return this;
}
+ /**
+ * Set the list of headers that a pre-flight request can list as allowed
+ * for use during an actual request.
+ * The special value {@code "*"} may be used to allow all headers.
+ * A header name is not required to be listed if it is one of:
+ * {@code Cache-Control}, {@code Content-Language}, {@code Expires},
+ * {@code Last-Modified}, or {@code Pragma} as per the CORS spec.
+ * By default all headers are allowed.
+ */
public CorsRegistration allowedHeaders(String... headers) {
this.config.setAllowedHeaders(new ArrayList<>(Arrays.asList(headers)));
return this;
}
+ /**
+ * Set the list of response headers other than "simple" headers, i.e.
+ * {@code Cache-Control}, {@code Content-Language}, {@code Content-Type},
+ * {@code Expires}, {@code Last-Modified}, or {@code Pragma}, that an
+ * actual response might have and can be exposed.
+ * Note that {@code "*"} is not supported on this property.
+ * By default this is not set.
+ */
public CorsRegistration exposedHeaders(String... headers) {
this.config.setExposedHeaders(new ArrayList<>(Arrays.asList(headers)));
return this;
}
+ /**
+ * Configure how long in seconds the response from a pre-flight request
+ * can be cached by clients.
+ * By default this is set to 1800 seconds (30 minutes).
+ */
public CorsRegistration maxAge(long maxAge) {
this.config.setMaxAge(maxAge);
return this;
}
+ /**
+ * Whether user credentials are supported.
+ * By default this is set to {@code true} in which case user credentials
+ * are supported.
+ */
public CorsRegistration allowCredentials(boolean allowCredentials) {
this.config.setAllowCredentials(allowCredentials);
return this;
diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/RequestMappingHandlerMapping.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/RequestMappingHandlerMapping.java
index ce740375c4..6d144bf18b 100644
--- a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/RequestMappingHandlerMapping.java
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/RequestMappingHandlerMapping.java
@@ -18,7 +18,6 @@ package org.springframework.web.servlet.mvc.method.annotation;
import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.Method;
-import java.util.Arrays;
import java.util.List;
import java.util.Set;
import javax.servlet.http.HttpServletRequest;
@@ -305,24 +304,12 @@ public class RequestMappingHandlerMapping extends RequestMappingInfoHandlerMappi
updateCorsConfig(config, typeAnnotation);
updateCorsConfig(config, methodAnnotation);
- if (CollectionUtils.isEmpty(config.getAllowedOrigins())) {
- config.setAllowedOrigins(Arrays.asList(CrossOrigin.DEFAULT_ORIGINS));
- }
if (CollectionUtils.isEmpty(config.getAllowedMethods())) {
for (RequestMethod allowedMethod : mappingInfo.getMethodsCondition().getMethods()) {
config.addAllowedMethod(allowedMethod.name());
}
}
- if (CollectionUtils.isEmpty(config.getAllowedHeaders())) {
- config.setAllowedHeaders(Arrays.asList(CrossOrigin.DEFAULT_ALLOWED_HEADERS));
- }
- if (config.getAllowCredentials() == null) {
- config.setAllowCredentials(CrossOrigin.DEFAULT_ALLOW_CREDENTIALS);
- }
- if (config.getMaxAge() == null) {
- config.setMaxAge(CrossOrigin.DEFAULT_MAX_AGE);
- }
- return config;
+ return config.applyPermitDefaultValues();
}
private void updateCorsConfig(CorsConfiguration config, CrossOrigin annotation) {
diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/config/MvcNamespaceTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/config/MvcNamespaceTests.java
index 6bdb0cd8ca..160149be1f 100644
--- a/spring-webmvc/src/test/java/org/springframework/web/servlet/config/MvcNamespaceTests.java
+++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/config/MvcNamespaceTests.java
@@ -882,7 +882,7 @@ public class MvcNamespaceTests {
assertArrayEquals(new String[]{"*"}, config.getAllowedHeaders().toArray());
assertNull(config.getExposedHeaders());
assertTrue(config.getAllowCredentials());
- assertEquals(new Long(1600), config.getMaxAge());
+ assertEquals(new Long(1800), config.getMaxAge());
}
}
@@ -912,7 +912,7 @@ public class MvcNamespaceTests {
assertArrayEquals(new String[]{"*"}, config.getAllowedHeaders().toArray());
assertNull(config.getExposedHeaders());
assertTrue(config.getAllowCredentials());
- assertEquals(new Long(1600), config.getMaxAge());
+ assertEquals(new Long(1800), config.getMaxAge());
}
}
+ *
+ */
+ public CorsConfiguration applyPermitDefaultValues() {
+ if (this.allowedOrigins == null) {
+ this.addAllowedOrigin(ALL);
+ }
+ if (this.allowedMethods == null) {
+ this.setAllowedMethods(Arrays.asList(
+ HttpMethod.GET.name(), HttpMethod.HEAD.name(), HttpMethod.POST.name()));
+ }
+ if (this.allowedHeaders == null) {
+ this.addAllowedHeader(ALL);
+ }
+ if (this.allowCredentials == null) {
+ this.setAllowCredentials(true);
+ }
+ if (this.maxAge == null) {
+ this.setMaxAge(1800L);
+ }
+ return this;
+ }
+
+ /**
+ * Combine the supplied {@code CorsConfiguration} with this one.
+ *