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] 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 122c55d2e2..5f28c1580c 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 5e1c6de00d..88ffe1bc96 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 c2249c4fa8..177d28b5a6 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 cd736034a3..8f6b16f1f7 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 327c83ff81..383505c4c7 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 523f5dcc0c..e1a25396c7 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) {