From 76b8bb2c751e5b8c9064b7f40898a89a23d24036 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Deleuze?= Date: Mon, 11 Sep 2023 15:34:46 +0200 Subject: [PATCH 1/2] Refine CORS documentation for wildcard processing This commit refines CORS wildcard processing Javadoc to provides more details on how wildcards are handled for Access-Control-Allow-Methods, Access-Control-Allow-Headers and Access-Control-Expose-Headers CORS headers. For Access-Control-Expose-Headers, it is not possible to copy the response headers which are not available at the point when the CorsProcessor is invoked. Since all the major browsers seem to support wildcard including on requests with credentials, and since this is ultimately the user-agent responsibility to check on client-side what is authorized or not, Spring Framework continues to support this use case. See gh-31143 --- .../web/bind/annotation/CrossOrigin.java | 27 +++------ .../web/cors/CorsConfiguration.java | 58 +++++++++++++------ .../web/cors/DefaultCorsProcessor.java | 2 +- .../cors/reactive/DefaultCorsProcessor.java | 2 +- .../web/reactive/config/CorsRegistration.java | 28 ++++----- .../config/annotation/CorsRegistration.java | 28 ++++----- 6 files changed, 79 insertions(+), 66 deletions(-) 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) { From c892ce5537748b0c1a05dd1ceacc71e4fc374b1d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Deleuze?= Date: Mon, 11 Sep 2023 17:00:32 +0200 Subject: [PATCH 2/2] Refine CORS documentation for wildcard processing This commit adds a reference documentation section dedicated to CORS credentialed requests and related wildcard processing. Closes gh-31143 --- .../modules/ROOT/pages/web/webflux-cors.adoc | 29 +++++++++++++++++++ .../modules/ROOT/pages/web/webmvc-cors.adoc | 29 +++++++++++++++++++ 2 files changed, 58 insertions(+) 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]#