diff --git a/framework-docs/modules/ROOT/pages/web/webflux-cors.adoc b/framework-docs/modules/ROOT/pages/web/webflux-cors.adoc index 8aefc7d58e8..845976b8e02 100644 --- a/framework-docs/modules/ROOT/pages/web/webflux-cors.adoc +++ b/framework-docs/modules/ROOT/pages/web/webflux-cors.adoc @@ -75,6 +75,35 @@ To learn more from the source or to make advanced customizations, see: +[[webflux-cors-credentialed-requests]] +== Credentialed Requests +[.small]#xref:web/webmvc-cors.adoc#mvc-cors-credentialed-requests[See equivalent in the Servlet stack]# + +Using CORS with credentialed requests requires enabling `allowedCredentials`. Be aware that +this option establishes a high level of trust with the configured domains and also increases +the surface of attack of the web application by exposing sensitive user-specific information +such as cookies and CSRF tokens. + +Enabling credentials also impacts how the configured `"*"` CORS wildcards are processed: + +* Wildcards are not authorized in `allowOrigins`, but alternatively +the `allowOriginPatterns` property may be used to match to a dynamic set of origins. +* When set on `allowedHeaders` or `allowedMethods`, the `Access-Control-Allow-Headers` +and `Access-Control-Allow-Methods` response headers are handled by copying the related +headers and method specified in the CORS preflight request. +* When set on `exposedHeaders`, `Access-Control-Expose-Headers` response header is set +either to the configured list of headers or to the wildcard character. While the CORS spec +does not allow the wildcard character when `Access-Control-Allow-Credentials` is set to +`true`, most browsers support it and the response headers are not all available during the +CORS processing, so as a consequence the wildcard character is the header value used when +specified regardless of the value of the `allowCredentials` property. + +WARNING: While such wildcard configuration can be handy, it is recommended when possible to configure +a finite set of values instead to provide a higher level of security. + + + + [[webflux-cors-controller]] == `@CrossOrigin` [.small]#xref:web/webmvc-cors.adoc#mvc-cors-controller[See equivalent in the Servlet stack]# diff --git a/framework-docs/modules/ROOT/pages/web/webmvc-cors.adoc b/framework-docs/modules/ROOT/pages/web/webmvc-cors.adoc index e959c141a1f..3a8596d8250 100644 --- a/framework-docs/modules/ROOT/pages/web/webmvc-cors.adoc +++ b/framework-docs/modules/ROOT/pages/web/webmvc-cors.adoc @@ -25,6 +25,35 @@ powerful workarounds based on IFRAME or JSONP. +[[mvc-cors-credentialed-requests]] +== Credentialed Requests +[.small]#xref:web/webflux-cors.adoc#webflux-cors-credentialed-requests[See equivalent in the Reactive stack]# + +Using CORS with credentialed requests requires enabling `allowedCredentials`. Be aware that +this option establishes a high level of trust with the configured domains and also increases +the surface of attack of the web application by exposing sensitive user-specific information +such as cookies and CSRF tokens. + +Enabling credentials also impacts how the configured `"*"` CORS wildcards are processed: + +* Wildcards are not authorized in `allowOrigins`, but alternatively +the `allowOriginPatterns` property may be used to match to a dynamic set of origins. +* When set on `allowedHeaders` or `allowedMethods`, the `Access-Control-Allow-Headers` +and `Access-Control-Allow-Methods` response headers are handled by copying the related +headers and method specified in the CORS preflight request. +* When set on `exposedHeaders`, `Access-Control-Expose-Headers` response header is set +either to the configured list of headers or to the wildcard character. While the CORS spec +does not allow the wildcard character when `Access-Control-Allow-Credentials` is set to +`true`, most browsers support it and the response headers are not all available during the +CORS processing, so as a consequence the wildcard character is the header value used when +specified regardless of the value of the `allowCredentials` property. + +WARNING: While such wildcard configuration can be handy, it is recommended when possible to configure +a finite set of values instead to provide a higher level of security. + + + + [[mvc-cors-processing]] == Processing [.small]#xref:web/webflux-cors.adoc#webflux-cors-processing[See equivalent in the Reactive stack]# 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 122c55d2e21..5f28c1580c1 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 @@ -1,5 +1,5 @@ /* - * Copyright 2002-2021 the original author or authors. + * Copyright 2002-2023 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. @@ -80,31 +80,23 @@ public @interface CrossOrigin { /** * The list of request headers that are permitted in actual requests, - * possibly {@code "*"} to allow all headers. - *

Allowed headers are listed in the {@code Access-Control-Allow-Headers} - * response header of preflight requests. - *

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. + * possibly {@code "*"} to allow all headers. Please, see + * {@link CorsConfiguration#setAllowedHeaders(List)} for details. *

By default all requested headers are allowed. */ String[] allowedHeaders() default {}; /** * The List of response headers that the user-agent will allow the client - * to access on an actual response, other than "simple" headers, i.e. - * {@code Cache-Control}, {@code Content-Language}, {@code Content-Type}, - * {@code Expires}, {@code Last-Modified}, or {@code Pragma}, - *

Exposed headers are listed in the {@code Access-Control-Expose-Headers} - * response header of actual CORS requests. - *

The special value {@code "*"} allows all headers to be exposed for - * non-credentialed requests. + * to access on an actual response, possibly {@code "*"} to expose all headers. + * Please, see {@link CorsConfiguration#setExposedHeaders(List)} for details. *

By default no headers are listed as exposed. */ String[] exposedHeaders() default {}; /** - * The list of supported HTTP request methods. + * The list of supported HTTP request methods. Please, see + * {@link CorsConfiguration#setAllowedMethods(List)} for details. *

By default the supported methods are the same as the ones to which a * controller method is mapped. */ @@ -112,9 +104,8 @@ public @interface CrossOrigin { /** * Whether the browser should send credentials, such as cookies along with - * cross domain requests, to the annotated endpoint. The configured value is - * set on the {@code Access-Control-Allow-Credentials} response header of - * preflight requests. + * cross domain requests, to the annotated endpoint. Please, see + * {@link CorsConfiguration#setAllowCredentials(Boolean)} for details. *

NOTE: Be aware that this option establishes a high * level of trust with the configured domains and also increases the surface * attack of the web application by exposing sensitive user-specific 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 5e1c6de00d7..88ffe1bc96b 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 @@ -280,8 +280,12 @@ public class CorsConfiguration { /** * Set the HTTP methods to allow, e.g. {@code "GET"}, {@code "POST"}, - * {@code "PUT"}, etc. - *

The special value {@code "*"} allows all methods. + * {@code "PUT"}, etc. The special value {@code "*"} allows all methods. + *

{@code Access-Control-Allow-Methods} response header is set either + * to the configured method or to {@code "*"}. Keep in mind however that the + * CORS spec does not allow {@code "*"} when {@link #setAllowCredentials + * allowCredentials} is set to {@code true}, that combination is handled + * by copying the method specified in the CORS preflight request. *

If not set, only {@code "GET"} and {@code "HEAD"} are allowed. *

By default this is not set. *

Note: CORS checks use values from "Forwarded" @@ -312,9 +316,9 @@ public class CorsConfiguration { /** * Return the allowed HTTP methods, or {@code null} in which case * only {@code "GET"} and {@code "HEAD"} allowed. + * @see #setAllowedMethods(List) * @see #addAllowedMethod(HttpMethod) * @see #addAllowedMethod(String) - * @see #setAllowedMethods(List) */ @Nullable public List getAllowedMethods() { @@ -322,14 +326,14 @@ public class CorsConfiguration { } /** - * Add an HTTP method to allow. + * Variant of {@link #setAllowedMethods} for adding one allowed method at a time. */ public void addAllowedMethod(HttpMethod method) { addAllowedMethod(method.name()); } /** - * Add an HTTP method to allow. + * Variant of {@link #setAllowedMethods} for adding one allowed method at a time. */ public void addAllowedMethod(String method) { if (StringUtils.hasText(method)) { @@ -352,9 +356,13 @@ public class CorsConfiguration { /** * 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. + * for use during an actual request. The special value {@code "*"} allows + * actual requests to send any header. + *

{@code Access-Control-Allow-Headers} response header is set either + * to the configured list of headers or to {@code "*"}. Keep in mind however + * that the CORS spec does not allow {@code "*"} when {@link #setAllowCredentials + * allowCredentials} is set to {@code true}, that combination is handled by + * copying the headers specified in the CORS preflight request. *

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}. @@ -375,7 +383,7 @@ public class CorsConfiguration { } /** - * Add an actual request header to allow. + * Variant of {@link #setAllowedHeaders(List)} for adding one allowed header at a time. */ public void addAllowedHeader(String allowedHeader) { if (this.allowedHeaders == null) { @@ -388,12 +396,19 @@ public class CorsConfiguration { } /** - * 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. - *

The special value {@code "*"} allows all headers to be exposed for - * non-credentialed requests. + * Set the list of response headers that an actual response might have + * and can be exposed to the client. The special value {@code "*"} + * allows all headers to be exposed. + *

{@code Access-Control-Expose-Headers} response header is set either + * to the configured list of headers or to {@code "*"}. While the CORS + * spec does not allow {@code "*"} when {@code Access-Control-Allow-Credentials} + * is set to {@code true}, most browsers support it and + * the response headers are not all available during the CORS processing, + * so as a consequence {@code "*"} is the header value used when specified + * regardless of the value of the `allowCredentials` property. + *

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 this is not set. */ public void setExposedHeaders(@Nullable List exposedHeaders) { @@ -411,9 +426,7 @@ public class CorsConfiguration { } /** - * Add a response header to expose. - *

The special value {@code "*"} allows all headers to be exposed for - * non-credentialed requests. + * Variant of {@link #setExposedHeaders} for adding one exposed header at a time. */ public void addExposedHeader(String exposedHeader) { if (this.exposedHeaders == null) { @@ -424,6 +437,15 @@ public class CorsConfiguration { /** * Whether user credentials are supported. + *

Setting this property has an impact on how {@link #setAllowedOrigins(List) + * origins}, {@link #setAllowedOriginPatterns(List) originPatterns}, + * {@link #setAllowedMethods(List) allowedMethods} and + * {@link #setAllowedHeaders(List) allowedHeaders} are processed, see related + * API documentation for more details. + *

NOTE: Be aware that this option establishes a high + * level of trust with the configured domains and also increases the surface + * attack of the web application by exposing sensitive user-specific + * information such as cookies and CSRF tokens. *

By default this is not set (i.e. user credentials are not supported). */ public void setAllowCredentials(@Nullable Boolean allowCredentials) { diff --git a/spring-web/src/main/java/org/springframework/web/cors/DefaultCorsProcessor.java b/spring-web/src/main/java/org/springframework/web/cors/DefaultCorsProcessor.java index c2249c4fa80..177d28b5a63 100644 --- a/spring-web/src/main/java/org/springframework/web/cors/DefaultCorsProcessor.java +++ b/spring-web/src/main/java/org/springframework/web/cors/DefaultCorsProcessor.java @@ -191,7 +191,7 @@ public class DefaultCorsProcessor implements CorsProcessor { /** * Check the headers and determine the headers for the response of a * pre-flight request. The default implementation simply delegates to - * {@link org.springframework.web.cors.CorsConfiguration#checkOrigin(String)}. + * {@link org.springframework.web.cors.CorsConfiguration#checkHeaders(List)}. */ @Nullable protected List checkHeaders(CorsConfiguration config, List requestHeaders) { diff --git a/spring-web/src/main/java/org/springframework/web/cors/reactive/DefaultCorsProcessor.java b/spring-web/src/main/java/org/springframework/web/cors/reactive/DefaultCorsProcessor.java index cd736034a37..8f6b16f1f7b 100644 --- a/spring-web/src/main/java/org/springframework/web/cors/reactive/DefaultCorsProcessor.java +++ b/spring-web/src/main/java/org/springframework/web/cors/reactive/DefaultCorsProcessor.java @@ -188,7 +188,7 @@ public class DefaultCorsProcessor implements CorsProcessor { /** * Check the headers and determine the headers for the response of a * pre-flight request. The default implementation simply delegates to - * {@link CorsConfiguration#checkOrigin(String)}. + * {@link CorsConfiguration#checkHeaders(List)}. */ @Nullable protected List checkHeaders(CorsConfiguration config, List requestHeaders) { diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/config/CorsRegistration.java b/spring-webflux/src/main/java/org/springframework/web/reactive/config/CorsRegistration.java index 327c83ff817..383505c4c7f 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/config/CorsRegistration.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/config/CorsRegistration.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2021 the original author or authors. + * Copyright 2002-2023 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. @@ -76,9 +76,11 @@ public class CorsRegistration { /** * 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} + * The special value {@code "*"} allows all methods. By default, + * "simple" methods {@code GET}, {@code HEAD}, and {@code POST} * are allowed. + *

Please, see {@link CorsConfiguration#setAllowedMethods(List)} for + * details. */ public CorsRegistration allowedMethods(String... methods) { this.config.setAllowedMethods(Arrays.asList(methods)); @@ -87,11 +89,10 @@ public class CorsRegistration { /** * 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. + * for use during an actual request. The special value {@code "*"} + * may be used to allow all headers. + *

Please, see {@link CorsConfiguration#setAllowedHeaders(List)} for + * details. *

By default all headers are allowed. */ public CorsRegistration allowedHeaders(String... headers) { @@ -100,12 +101,11 @@ public class CorsRegistration { } /** - * 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. - *

The special value {@code "*"} allows all headers to be exposed for - * non-credentialed requests. + * Set the list of response headers that an actual response might have and + * can be exposed. The special value {@code "*"} allows all headers to be + * exposed. + *

Please, see {@link CorsConfiguration#setExposedHeaders(List)} for + * details. *

By default this is not set. */ public CorsRegistration exposedHeaders(String... headers) { diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/config/annotation/CorsRegistration.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/config/annotation/CorsRegistration.java index 523f5dcc0c5..e1a25396c7c 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/config/annotation/CorsRegistration.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/config/annotation/CorsRegistration.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2021 the original author or authors. + * Copyright 2002-2023 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. @@ -77,9 +77,11 @@ public class CorsRegistration { /** * 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} + * The special value {@code "*"} allows all methods. By default, + * "simple" methods {@code GET}, {@code HEAD}, and {@code POST} * are allowed. + *

Please, see {@link CorsConfiguration#setAllowedMethods(List)} for + * details. */ public CorsRegistration allowedMethods(String... methods) { this.config.setAllowedMethods(Arrays.asList(methods)); @@ -88,11 +90,10 @@ public class CorsRegistration { /** * 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. + * for use during an actual request. The special value {@code "*"} + * may be used to allow all headers. + *

Please, see {@link CorsConfiguration#setAllowedHeaders(List)} for + * details. *

By default all headers are allowed. */ public CorsRegistration allowedHeaders(String... headers) { @@ -101,12 +102,11 @@ public class CorsRegistration { } /** - * 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. - *

The special value {@code "*"} allows all headers to be exposed for - * non-credentialed requests. + * Set the list of response headers that an actual response might have and + * can be exposed. The special value {@code "*"} allows all headers to be + * exposed. + *

Please, see {@link CorsConfiguration#setExposedHeaders(List)} for + * details. *

By default this is not set. */ public CorsRegistration exposedHeaders(String... headers) {