Skip to content

Commit 8c3a05b

Browse files
committed
Allow "*" for Access-Control-Expose-Headers
Closes gh-26113
1 parent 684e695 commit 8c3a05b

File tree

6 files changed

+23
-25
lines changed

6 files changed

+23
-25
lines changed

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,8 @@
114114
* {@code Expires}, {@code Last-Modified}, or {@code Pragma},
115115
* <p>Exposed headers are listed in the {@code Access-Control-Expose-Headers}
116116
* response header of actual CORS requests.
117+
* <p>The special value {@code "*"} allows all headers to be exposed for
118+
* non-credentialed requests.
117119
* <p>By default no headers are listed as exposed.
118120
*/
119121
String[] exposedHeaders() default {};

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

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -329,13 +329,11 @@ else if (this.allowedHeaders == DEFAULT_PERMIT_ALL) {
329329
* {@code Cache-Control}, {@code Content-Language}, {@code Content-Type},
330330
* {@code Expires}, {@code Last-Modified}, or {@code Pragma}) that an
331331
* actual response might have and can be exposed.
332-
* <p>Note that {@code "*"} is not a valid exposed header value.
332+
* <p>The special value {@code "*"} allows all headers to be exposed for
333+
* non-credentialed requests.
333334
* <p>By default this is not set.
334335
*/
335336
public void setExposedHeaders(@Nullable List<String> exposedHeaders) {
336-
if (exposedHeaders != null && exposedHeaders.contains(ALL)) {
337-
throw new IllegalArgumentException("'*' is not a valid exposed header value");
338-
}
339337
this.exposedHeaders = (exposedHeaders != null ? new ArrayList<>(exposedHeaders) : null);
340338
}
341339

@@ -351,12 +349,10 @@ public List<String> getExposedHeaders() {
351349

352350
/**
353351
* Add a response header to expose.
354-
* <p>Note that {@code "*"} is not a valid exposed header value.
352+
* <p>The special value {@code "*"} allows all headers to be exposed for
353+
* non-credentialed requests.
355354
*/
356355
public void addExposedHeader(String exposedHeader) {
357-
if (ALL.equals(exposedHeader)) {
358-
throw new IllegalArgumentException("'*' is not a valid exposed header value");
359-
}
360356
if (this.exposedHeaders == null) {
361357
this.exposedHeaders = new ArrayList<>(4);
362358
}

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

Lines changed: 12 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -61,32 +61,19 @@ public void setValues() {
6161
config.addAllowedOriginPattern("http://*.example.com");
6262
config.addAllowedHeader("*");
6363
config.addAllowedMethod("*");
64-
config.addExposedHeader("header1");
65-
config.addExposedHeader("header2");
64+
config.addExposedHeader("*");
6665
config.setAllowCredentials(true);
6766
config.setMaxAge(123L);
6867

6968
assertThat(config.getAllowedOrigins()).containsExactly("*");
7069
assertThat(config.getAllowedOriginPatterns()).containsExactly("http://*.example.com");
7170
assertThat(config.getAllowedHeaders()).containsExactly("*");
7271
assertThat(config.getAllowedMethods()).containsExactly("*");
73-
assertThat(config.getExposedHeaders()).containsExactly("header1", "header2");
72+
assertThat(config.getExposedHeaders()).containsExactly("*");
7473
assertThat(config.getAllowCredentials()).isTrue();
7574
assertThat(config.getMaxAge()).isEqualTo(new Long(123));
7675
}
7776

78-
@Test
79-
public void asteriskWildCardOnAddExposedHeader() {
80-
assertThatIllegalArgumentException()
81-
.isThrownBy(() -> new CorsConfiguration().addExposedHeader("*"));
82-
}
83-
84-
@Test
85-
public void asteriskWildCardOnSetExposedHeaders() {
86-
assertThatIllegalArgumentException()
87-
.isThrownBy(() -> new CorsConfiguration().setExposedHeaders(Collections.singletonList("*")));
88-
}
89-
9077
@Test
9178
public void combineWithNull() {
9279
CorsConfiguration config = new CorsConfiguration();
@@ -133,26 +120,30 @@ public void combineWithDefaultPermitValues() {
133120
assertThat(combinedConfig.getAllowedOrigins()).containsExactly("https://domain.com");
134121
assertThat(combinedConfig.getAllowedHeaders()).containsExactly("header1");
135122
assertThat(combinedConfig.getAllowedMethods()).containsExactly(HttpMethod.PUT.name());
123+
assertThat(combinedConfig.getExposedHeaders()).isEmpty();
136124

137125
combinedConfig = other.combine(config);
138126
assertThat(combinedConfig).isNotNull();
139127
assertThat(combinedConfig.getAllowedOrigins()).containsExactly("https://domain.com");
140128
assertThat(combinedConfig.getAllowedHeaders()).containsExactly("header1");
141129
assertThat(combinedConfig.getAllowedMethods()).containsExactly(HttpMethod.PUT.name());
130+
assertThat(combinedConfig.getExposedHeaders()).isEmpty();
142131

143132
combinedConfig = config.combine(new CorsConfiguration());
144133
assertThat(config.getAllowedOrigins()).containsExactly("*");
145134
assertThat(config.getAllowedHeaders()).containsExactly("*");
146135
assertThat(combinedConfig).isNotNull();
147136
assertThat(combinedConfig.getAllowedMethods())
148137
.containsExactly(HttpMethod.GET.name(), HttpMethod.HEAD.name(), HttpMethod.POST.name());
138+
assertThat(combinedConfig.getExposedHeaders()).isEmpty();
149139

150140
combinedConfig = new CorsConfiguration().combine(config);
151141
assertThat(config.getAllowedOrigins()).containsExactly("*");
152142
assertThat(config.getAllowedHeaders()).containsExactly("*");
153143
assertThat(combinedConfig).isNotNull();
154144
assertThat(combinedConfig.getAllowedMethods())
155145
.containsExactly(HttpMethod.GET.name(), HttpMethod.HEAD.name(), HttpMethod.POST.name());
146+
assertThat(combinedConfig.getExposedHeaders()).isEmpty();
156147
}
157148

158149
@Test
@@ -196,6 +187,7 @@ public void combineWithAsteriskWildCard() {
196187
CorsConfiguration config = new CorsConfiguration();
197188
config.addAllowedOrigin("*");
198189
config.addAllowedHeader("*");
190+
config.addExposedHeader("*");
199191
config.addAllowedMethod("*");
200192
config.addAllowedOriginPattern("*");
201193

@@ -204,21 +196,26 @@ public void combineWithAsteriskWildCard() {
204196
other.addAllowedOriginPattern("http://*.company.com");
205197
other.addAllowedHeader("header1");
206198
other.addExposedHeader("header2");
199+
other.addAllowedHeader("anotherHeader1");
200+
other.addExposedHeader("anotherHeader2");
207201
other.addAllowedMethod(HttpMethod.PUT.name());
208202

209203
CorsConfiguration combinedConfig = config.combine(other);
210204
assertThat(combinedConfig).isNotNull();
211205
assertThat(combinedConfig.getAllowedOrigins()).containsExactly("*");
212206
assertThat(combinedConfig.getAllowedOriginPatterns()).containsExactly("*");
213207
assertThat(combinedConfig.getAllowedHeaders()).containsExactly("*");
208+
assertThat(combinedConfig.getExposedHeaders()).containsExactly("*");
214209
assertThat(combinedConfig.getAllowedMethods()).containsExactly("*");
215210

216211
combinedConfig = other.combine(config);
217212
assertThat(combinedConfig).isNotNull();
218213
assertThat(combinedConfig.getAllowedOrigins()).containsExactly("*");
219214
assertThat(combinedConfig.getAllowedOriginPatterns()).containsExactly("*");
220215
assertThat(combinedConfig.getAllowedHeaders()).containsExactly("*");
216+
assertThat(combinedConfig.getExposedHeaders()).containsExactly("*");
221217
assertThat(combinedConfig.getAllowedMethods()).containsExactly("*");
218+
assertThat(combinedConfig.getAllowedHeaders()).containsExactly("*");
222219
}
223220

224221
@Test // SPR-14792

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,8 @@ public CorsRegistration allowedHeaders(String... headers) {
9898
* {@code Cache-Control}, {@code Content-Language}, {@code Content-Type},
9999
* {@code Expires}, {@code Last-Modified}, or {@code Pragma}, that an
100100
* actual response might have and can be exposed.
101-
* <p>Note that {@code "*"} is not supported on this property.
101+
* <p>The special value {@code "*"} allows all headers to be exposed for
102+
* non-credentialed requests.
102103
* <p>By default this is not set.
103104
*/
104105
public CorsRegistration exposedHeaders(String... headers) {

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,8 @@ public CorsRegistration allowedHeaders(String... headers) {
9999
* {@code Cache-Control}, {@code Content-Language}, {@code Content-Type},
100100
* {@code Expires}, {@code Last-Modified}, or {@code Pragma}, that an
101101
* actual response might have and can be exposed.
102-
* <p>Note that {@code "*"} is not supported on this property.
102+
* <p>The special value {@code "*"} allows all headers to be exposed for
103+
* non-credentialed requests.
103104
* <p>By default this is not set.
104105
*/
105106
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
@@ -1400,6 +1400,7 @@
14001400
Comma-separated list of response headers other than simple headers (i.e.
14011401
Cache-Control, Content-Language, Content-Type, Expires, Last-Modified, Pragma) that an
14021402
actual response might have and can be exposed.
1403+
The special value "*" allows all headers to be exposed for non-credentialed requests.
14031404
Empty by default.
14041405
]]></xsd:documentation>
14051406
</xsd:annotation>

0 commit comments

Comments
 (0)