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..e209a2628e 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,18 +19,14 @@ 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. + *
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. * * @author Sebastien Deleuze * @since 5.0 @@ -43,44 +39,84 @@ public class CorsRegistration { private final CorsConfiguration config; + /** + * Create a new {@link CorsRegistration} instance with all origins, headers, credentials + * and {@code GET}, {@code HEAD}, and {@code POST} methods allowed, and the max age + * set to 30 minutes on the specified path. + * @param pathPattern the path where the CORS configuration should apply. 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().applyDefaultPermitConfiguration(); } + /** + * 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"}, + * {@code "PUT"}, 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 "*"} allows actual requests to send any + * header. + *
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}. + *
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 a valid exposed header value. + *
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} (i.e. 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..e0fdd20de7 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.applyDefaultPermitConfiguration(); } 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..012c1e187c 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,13 @@ 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, headers are permitted, credentials are allowed and the + * maximum age is set to 30 minutes. * *
NOTE: {@code @CrossOrigin} is processed if an appropriate * {@code HandlerMapping}-{@code HandlerAdapter} pair is configured such as the @@ -44,12 +46,28 @@ import org.springframework.core.annotation.AliasFor; @Documented public @interface CrossOrigin { + /** + * @deprecated as of Spring 5.0, in favor of using {@link CorsConfiguration#applyDefaultPermitConfiguration} + */ + @Deprecated String[] DEFAULT_ORIGINS = { "*" }; + /** + * @deprecated as of Spring 5.0, in favor of using {@link CorsConfiguration#applyDefaultPermitConfiguration} + */ + @Deprecated String[] DEFAULT_ALLOWED_HEADERS = { "*" }; + /** + * @deprecated as of Spring 5.0, in favor of using {@link CorsConfiguration#applyDefaultPermitConfiguration} + */ + @Deprecated boolean DEFAULT_ALLOW_CREDENTIALS = true; + /** + * @deprecated as of Spring 5.0, in favor of using {@link CorsConfiguration#applyDefaultPermitConfiguration} + */ + @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..c4258833df 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; @@ -71,7 +72,9 @@ public class CorsConfiguration { /** - * Construct a new, empty {@code CorsConfiguration} instance. + * Construct a new {@code CorsConfiguration} instance with nothing allowed by default + * but {@code "GET"} and {@code "HEAD"} methods. + * @see #applyDefaultPermitConfiguration() */ public CorsConfiguration() { } @@ -90,6 +93,36 @@ public class CorsConfiguration { this.maxAge = other.maxAge; } + /** + * Apply the following default permit configuration only for properties that has not + * been defined: + *
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. + *
By default, all origins, headers, credentials and {@code GET}, {@code HEAD}, and + * {@code POST} methods are allowed, and the max age is set to 30 minutes. * * @author Sebastien Deleuze * @author Sam Brannen @@ -47,43 +41,84 @@ public class CorsRegistration { private final CorsConfiguration config; + /** + * Create a new {@link CorsRegistration} instance with all origins, headers, credentials + * and {@code GET}, {@code HEAD}, and {@code POST} methods allowed, and the max age + * set to 30 minutes on the specified path. + * @param pathPattern the path where the CORS configuration should apply. 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().applyDefaultPermitConfiguration(); } + /** + * 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"}, + * {@code "PUT"}, 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 "*"} allows actual requests to send any + * header. + *
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}. + *
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 a valid exposed header value. + *
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} (i.e. 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..7f0c24f493 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.applyDefaultPermitConfiguration(); } 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()); } }