Skip to content

Commit d7f891b

Browse files
Pedro Ivo Machadowilkinsona
Pedro Ivo Machado
authored andcommitted
Add config prop for endpoints' CORS allowed origin patterns
See gh-24680
1 parent 743343c commit d7f891b

File tree

3 files changed

+65
-2
lines changed

3 files changed

+65
-2
lines changed

spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/CorsEndpointProperties.java

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,10 +38,17 @@ public class CorsEndpointProperties {
3838

3939
/**
4040
* Comma-separated list of origins to allow. '*' allows all origins. When not set,
41-
* CORS support is disabled.
41+
* CORS support is disabled. When credentials are supported only explicit urls are
42+
* allowed.
4243
*/
4344
private List<String> allowedOrigins = new ArrayList<>();
4445

46+
/**
47+
* Comma-separated list of origins patterns to allow. Must be used when credentials
48+
* are supported and do you want to use wildcard urls.
49+
*/
50+
private List<String> allowedOriginPatterns = new ArrayList<>();
51+
4552
/**
4653
* Comma-separated list of methods to allow. '*' allows all methods. When not set,
4754
* defaults to GET.
@@ -78,6 +85,14 @@ public void setAllowedOrigins(List<String> allowedOrigins) {
7885
this.allowedOrigins = allowedOrigins;
7986
}
8087

88+
public List<String> getAllowedOriginPatterns() {
89+
return this.allowedOriginPatterns;
90+
}
91+
92+
public void setAllowedOriginPatterns(List<String> allowedOriginPatterns) {
93+
this.allowedOriginPatterns = allowedOriginPatterns;
94+
}
95+
8196
public List<String> getAllowedMethods() {
8297
return this.allowedMethods;
8398
}
@@ -119,12 +134,13 @@ public void setMaxAge(Duration maxAge) {
119134
}
120135

121136
public CorsConfiguration toCorsConfiguration() {
122-
if (CollectionUtils.isEmpty(this.allowedOrigins)) {
137+
if (CollectionUtils.isEmpty(this.allowedOrigins) && CollectionUtils.isEmpty(this.allowedOriginPatterns)) {
123138
return null;
124139
}
125140
PropertyMapper map = PropertyMapper.get();
126141
CorsConfiguration configuration = new CorsConfiguration();
127142
map.from(this::getAllowedOrigins).to(configuration::setAllowedOrigins);
143+
map.from(this::getAllowedOriginPatterns).to(configuration::setAllowedOriginPatterns);
128144
map.from(this::getAllowedHeaders).whenNot(CollectionUtils::isEmpty).to(configuration::setAllowedHeaders);
129145
map.from(this::getAllowedMethods).whenNot(CollectionUtils::isEmpty).to(configuration::setAllowedMethods);
130146
map.from(this::getExposedHeaders).whenNot(CollectionUtils::isEmpty).to(configuration::setExposedHeaders);

spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/integrationtest/WebFluxEndpointCorsIntegrationTests.java

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,9 @@
1616

1717
package org.springframework.boot.actuate.autoconfigure.integrationtest;
1818

19+
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.options;
20+
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
21+
1922
import java.util.function.Consumer;
2023

2124
import org.junit.jupiter.api.Test;
@@ -145,6 +148,29 @@ void credentialsCanBeDisabled() {
145148
.expectHeader().doesNotExist(HttpHeaders.ACCESS_CONTROL_ALLOW_CREDENTIALS)));
146149
}
147150

151+
@Test
152+
void settingAllowedOriginsPattern() {
153+
this.contextRunner
154+
.withPropertyValues("management.endpoints.web.cors.allowed-origin-patterns:*.example.com",
155+
"management.endpoints.web.cors.allow-credentials:true")
156+
.run(withWebTestClient((webTestClient) -> webTestClient.options().uri("/actuator/beans")
157+
.header("Origin", "spring.example.com")
158+
.header(HttpHeaders.ACCESS_CONTROL_REQUEST_METHOD, "HEAD").exchange().expectStatus().isOk()
159+
.expectHeader().valueEquals(HttpHeaders.ACCESS_CONTROL_ALLOW_METHODS, "GET,HEAD")));
160+
}
161+
162+
@Test
163+
void requestsWithDisallowedOriginPatternsAreRejected() {
164+
this.contextRunner
165+
.withPropertyValues("management.endpoints.web.cors.allowed-origin-patterns:*.example.com",
166+
"management.endpoints.web.cors.allow-credentials:true")
167+
.run(withWebTestClient((webTestClient) -> webTestClient.options().uri("/actuator/beans")
168+
.header("Origin", "spring.example.org")
169+
.header(HttpHeaders.ACCESS_CONTROL_REQUEST_METHOD, "HEAD").exchange().expectStatus()
170+
.isForbidden()));
171+
172+
}
173+
148174
private ContextConsumer<ReactiveWebApplicationContext> withWebTestClient(Consumer<WebTestClient> webTestClient) {
149175
return (context) -> webTestClient.accept(WebTestClient.bindToApplicationContext(context).configureClient()
150176
.baseUrl("https://spring.example.org").build());

spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/integrationtest/WebMvcEndpointCorsIntegrationTests.java

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,27 @@ void credentialsCanBeDisabled() {
156156
.andExpect(header().doesNotExist(HttpHeaders.ACCESS_CONTROL_ALLOW_CREDENTIALS))));
157157
}
158158

159+
@Test
160+
void settingAllowedOriginsPattern() {
161+
this.contextRunner.withPropertyValues("management.endpoints.web.cors.allowed-origin-patterns:*.example.com",
162+
"management.endpoints.web.cors.allow-credentials:true").run(withMockMvc((mockMvc) -> {
163+
mockMvc.perform(options("/actuator/beans").header("Origin", "bar.example.com")
164+
.header(HttpHeaders.ACCESS_CONTROL_REQUEST_METHOD, "GET")).andExpect(status().isOk());
165+
performAcceptedCorsRequest(mockMvc);
166+
}));
167+
}
168+
169+
@Test
170+
void requestsWithDisallowedOriginPatternsAreRejected() {
171+
this.contextRunner.withPropertyValues("management.endpoints.web.cors.allowed-origin-patterns:*.example.com",
172+
"management.endpoints.web.cors.allow-credentials:true").run(withMockMvc((mockMvc) -> {
173+
mockMvc.perform(options("/actuator/beans").header("Origin", "bar.domain.com")
174+
.header(HttpHeaders.ACCESS_CONTROL_REQUEST_METHOD, "GET"))
175+
.andExpect(status().isForbidden());
176+
performAcceptedCorsRequest(mockMvc);
177+
}));
178+
}
179+
159180
private ContextConsumer<WebApplicationContext> withMockMvc(MockMvcConsumer mockMvc) {
160181
return (context) -> mockMvc.accept(MockMvcBuilders.webAppContextSetup(context).build());
161182
}

0 commit comments

Comments
 (0)