Skip to content

Commit 37bda56

Browse files
committed
Allow "*" for Access-Control-Expose-Headers
Closes gh-26113
1 parent 322babc commit 37bda56

File tree

6 files changed

+54
-53
lines changed

6 files changed

+54
-53
lines changed

spring-web/src/main/java/org/springframework/web/bind/annotation/CrossOrigin.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2019 the original author or authors.
2+
* Copyright 2002-2020 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -112,6 +112,8 @@
112112
* {@code Expires}, {@code Last-Modified}, or {@code Pragma},
113113
* <p>Exposed headers are listed in the {@code Access-Control-Expose-Headers}
114114
* response header of actual CORS requests.
115+
* <p>The special value {@code "*"} allows all headers to be exposed for
116+
* non-credentialed requests.
115117
* <p>By default no headers are listed as exposed.
116118
*/
117119
String[] exposedHeaders() default {};

spring-web/src/main/java/org/springframework/web/cors/CorsConfiguration.java

Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2019 the original author or authors.
2+
* Copyright 2002-2020 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -254,13 +254,11 @@ else if (this.allowedHeaders == DEFAULT_PERMIT_ALL) {
254254
* {@code Cache-Control}, {@code Content-Language}, {@code Content-Type},
255255
* {@code Expires}, {@code Last-Modified}, or {@code Pragma}) that an
256256
* actual response might have and can be exposed.
257-
* <p>Note that {@code "*"} is not a valid exposed header value.
257+
* <p>The special value {@code "*"} allows all headers to be exposed for
258+
* non-credentialed requests.
258259
* <p>By default this is not set.
259260
*/
260261
public void setExposedHeaders(@Nullable List<String> exposedHeaders) {
261-
if (exposedHeaders != null && exposedHeaders.contains(ALL)) {
262-
throw new IllegalArgumentException("'*' is not a valid exposed header value");
263-
}
264262
this.exposedHeaders = (exposedHeaders != null ? new ArrayList<>(exposedHeaders) : null);
265263
}
266264

@@ -276,12 +274,10 @@ public List<String> getExposedHeaders() {
276274

277275
/**
278276
* Add a response header to expose.
279-
* <p>Note that {@code "*"} is not a valid exposed header value.
277+
* <p>The special value {@code "*"} allows all headers to be exposed for
278+
* non-credentialed requests.
280279
*/
281280
public void addExposedHeader(String exposedHeader) {
282-
if (ALL.equals(exposedHeader)) {
283-
throw new IllegalArgumentException("'*' is not a valid exposed header value");
284-
}
285281
if (this.exposedHeaders == null) {
286282
this.exposedHeaders = new ArrayList<>(4);
287283
}

spring-web/src/test/java/org/springframework/web/cors/CorsConfigurationTests.java

Lines changed: 39 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2019 the original author or authors.
2+
* Copyright 2002-2020 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -25,7 +25,6 @@
2525
import org.springframework.http.HttpMethod;
2626

2727
import static org.assertj.core.api.Assertions.assertThat;
28-
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
2928

3029
/**
3130
* Unit tests for {@link CorsConfiguration}.
@@ -61,29 +60,13 @@ public void setValues() {
6160
assertThat(config.getAllowedHeaders()).isEqualTo(Arrays.asList("*"));
6261
config.addAllowedMethod("*");
6362
assertThat(config.getAllowedMethods()).isEqualTo(Arrays.asList("*"));
64-
config.addExposedHeader("header1");
65-
config.addExposedHeader("header2");
66-
assertThat(config.getExposedHeaders()).isEqualTo(Arrays.asList("header1", "header2"));
63+
config.addExposedHeader("*");
6764
config.setAllowCredentials(true);
6865
assertThat((boolean) config.getAllowCredentials()).isTrue();
6966
config.setMaxAge(123L);
7067
assertThat(config.getMaxAge()).isEqualTo(new Long(123));
7168
}
7269

73-
@Test
74-
public void asteriskWildCardOnAddExposedHeader() {
75-
CorsConfiguration config = new CorsConfiguration();
76-
assertThatIllegalArgumentException().isThrownBy(() ->
77-
config.addExposedHeader("*"));
78-
}
79-
80-
@Test
81-
public void asteriskWildCardOnSetExposedHeaders() {
82-
CorsConfiguration config = new CorsConfiguration();
83-
assertThatIllegalArgumentException().isThrownBy(() ->
84-
config.setExposedHeaders(Arrays.asList("*")));
85-
}
86-
8770
@Test
8871
public void combineWithNull() {
8972
CorsConfiguration config = new CorsConfiguration();
@@ -120,47 +103,64 @@ public void combineWithDefaultPermitValues() {
120103
other.addAllowedMethod(HttpMethod.PUT.name());
121104

122105
CorsConfiguration combinedConfig = config.combine(other);
123-
assertThat(combinedConfig.getAllowedOrigins()).isEqualTo(Arrays.asList("https://domain.com"));
124-
assertThat(combinedConfig.getAllowedHeaders()).isEqualTo(Arrays.asList("header1"));
125-
assertThat(combinedConfig.getAllowedMethods()).isEqualTo(Arrays.asList(HttpMethod.PUT.name()));
106+
assertThat(combinedConfig).isNotNull();
107+
assertThat(combinedConfig.getAllowedOrigins()).containsExactly("https://domain.com");
108+
assertThat(combinedConfig.getAllowedHeaders()).containsExactly("header1");
109+
assertThat(combinedConfig.getAllowedMethods()).containsExactly(HttpMethod.PUT.name());
110+
assertThat(combinedConfig.getExposedHeaders()).isEmpty();
126111

127112
combinedConfig = other.combine(config);
128-
assertThat(combinedConfig.getAllowedOrigins()).isEqualTo(Arrays.asList("https://domain.com"));
129-
assertThat(combinedConfig.getAllowedHeaders()).isEqualTo(Arrays.asList("header1"));
130-
assertThat(combinedConfig.getAllowedMethods()).isEqualTo(Arrays.asList(HttpMethod.PUT.name()));
113+
assertThat(combinedConfig).isNotNull();
114+
assertThat(combinedConfig.getAllowedOrigins()).containsExactly("https://domain.com");
115+
assertThat(combinedConfig.getAllowedHeaders()).containsExactly("header1");
116+
assertThat(combinedConfig.getAllowedMethods()).containsExactly(HttpMethod.PUT.name());
117+
assertThat(combinedConfig.getExposedHeaders()).isEmpty();
131118

132119
combinedConfig = config.combine(new CorsConfiguration());
133-
assertThat(config.getAllowedOrigins()).isEqualTo(Arrays.asList("*"));
134-
assertThat(config.getAllowedHeaders()).isEqualTo(Arrays.asList("*"));
135-
assertThat(combinedConfig.getAllowedMethods()).isEqualTo(Arrays.asList(HttpMethod.GET.name(), HttpMethod.HEAD.name(),
136-
HttpMethod.POST.name()));
120+
assertThat(config.getAllowedOrigins()).containsExactly("*");
121+
assertThat(config.getAllowedHeaders()).containsExactly("*");
122+
assertThat(combinedConfig).isNotNull();
123+
assertThat(combinedConfig.getAllowedMethods())
124+
.containsExactly(HttpMethod.GET.name(), HttpMethod.HEAD.name(), HttpMethod.POST.name());
125+
assertThat(combinedConfig.getExposedHeaders()).isEmpty();
137126

138127
combinedConfig = new CorsConfiguration().combine(config);
139-
assertThat(config.getAllowedOrigins()).isEqualTo(Arrays.asList("*"));
140-
assertThat(config.getAllowedHeaders()).isEqualTo(Arrays.asList("*"));
141-
assertThat(combinedConfig.getAllowedMethods()).isEqualTo(Arrays.asList(HttpMethod.GET.name(), HttpMethod.HEAD.name(),
142-
HttpMethod.POST.name()));
128+
assertThat(config.getAllowedOrigins()).containsExactly("*");
129+
assertThat(config.getAllowedHeaders()).containsExactly("*");
130+
assertThat(combinedConfig).isNotNull();
131+
assertThat(combinedConfig.getAllowedMethods())
132+
.containsExactly(HttpMethod.GET.name(), HttpMethod.HEAD.name(), HttpMethod.POST.name());
133+
assertThat(combinedConfig.getExposedHeaders()).isEmpty();
143134
}
144135

145136
@Test
146137
public void combineWithAsteriskWildCard() {
147138
CorsConfiguration config = new CorsConfiguration();
148139
config.addAllowedOrigin("*");
149140
config.addAllowedHeader("*");
141+
config.addExposedHeader("*");
150142
config.addAllowedMethod("*");
151143
CorsConfiguration other = new CorsConfiguration();
152144
other.addAllowedOrigin("https://domain.com");
153145
other.addAllowedHeader("header1");
154146
other.addExposedHeader("header2");
147+
other.addAllowedHeader("anotherHeader1");
148+
other.addExposedHeader("anotherHeader2");
155149
other.addAllowedMethod(HttpMethod.PUT.name());
156150
CorsConfiguration combinedConfig = config.combine(other);
157-
assertThat(combinedConfig.getAllowedOrigins()).isEqualTo(Arrays.asList("*"));
158-
assertThat(combinedConfig.getAllowedHeaders()).isEqualTo(Arrays.asList("*"));
159-
assertThat(combinedConfig.getAllowedMethods()).isEqualTo(Arrays.asList("*"));
151+
assertThat(combinedConfig).isNotNull();
152+
assertThat(combinedConfig.getAllowedOrigins()).containsExactly("*");
153+
assertThat(combinedConfig.getAllowedHeaders()).containsExactly("*");
154+
assertThat(combinedConfig.getExposedHeaders()).containsExactly("*");
155+
assertThat(combinedConfig.getAllowedMethods()).containsExactly("*");
156+
160157
combinedConfig = other.combine(config);
161-
assertThat(combinedConfig.getAllowedOrigins()).isEqualTo(Arrays.asList("*"));
162-
assertThat(combinedConfig.getAllowedHeaders()).isEqualTo(Arrays.asList("*"));
163-
assertThat(combinedConfig.getAllowedMethods()).isEqualTo(Arrays.asList("*"));
158+
assertThat(combinedConfig).isNotNull();
159+
assertThat(combinedConfig.getAllowedOrigins()).containsExactly("*");
160+
assertThat(combinedConfig.getAllowedHeaders()).containsExactly("*");
161+
assertThat(combinedConfig.getExposedHeaders()).containsExactly("*");
162+
assertThat(combinedConfig.getAllowedMethods()).containsExactly("*");
163+
assertThat(combinedConfig.getAllowedHeaders()).containsExactly("*");
164164
}
165165

166166
@Test // SPR-14792

spring-webflux/src/main/java/org/springframework/web/reactive/config/CorsRegistration.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2018 the original author or authors.
2+
* Copyright 2002-2020 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -93,7 +93,8 @@ public CorsRegistration allowedHeaders(String... headers) {
9393
* {@code Cache-Control}, {@code Content-Language}, {@code Content-Type},
9494
* {@code Expires}, {@code Last-Modified}, or {@code Pragma}, that an
9595
* actual response might have and can be exposed.
96-
* <p>Note that {@code "*"} is not supported on this property.
96+
* <p>The special value {@code "*"} allows all headers to be exposed for
97+
* non-credentialed requests.
9798
* <p>By default this is not set.
9899
*/
99100
public CorsRegistration exposedHeaders(String... headers) {

spring-webmvc/src/main/java/org/springframework/web/servlet/config/annotation/CorsRegistration.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2018 the original author or authors.
2+
* Copyright 2002-2020 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -95,7 +95,8 @@ public CorsRegistration allowedHeaders(String... headers) {
9595
* {@code Cache-Control}, {@code Content-Language}, {@code Content-Type},
9696
* {@code Expires}, {@code Last-Modified}, or {@code Pragma}, that an
9797
* actual response might have and can be exposed.
98-
* <p>Note that {@code "*"} is not supported on this property.
98+
* <p>The special value {@code "*"} allows all headers to be exposed for
99+
* non-credentialed requests.
99100
* <p>By default this is not set.
100101
*/
101102
public CorsRegistration exposedHeaders(String... headers) {

spring-webmvc/src/main/resources/org/springframework/web/servlet/config/spring-mvc.xsd

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1377,6 +1377,7 @@
13771377
Comma-separated list of response headers other than simple headers (i.e.
13781378
Cache-Control, Content-Language, Content-Type, Expires, Last-Modified, Pragma) that an
13791379
actual response might have and can be exposed.
1380+
The special value "*" allows all headers to be exposed for non-credentialed requests.
13801381
Empty by default.
13811382
]]></xsd:documentation>
13821383
</xsd:annotation>

0 commit comments

Comments
 (0)