Polish "Add config prop for endpoints' CORS allowed origin patterns"

See gh-24680
This commit is contained in:
Andy Wilkinson 2021-01-19 15:51:14 +00:00
parent d7f891be39
commit b095c7761a
3 changed files with 35 additions and 55 deletions

View File

@ -1,5 +1,5 @@
/* /*
* Copyright 2012-2019 the original author or authors. * Copyright 2012-2021 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -37,15 +37,18 @@ import org.springframework.web.cors.CorsConfiguration;
public class CorsEndpointProperties { public class CorsEndpointProperties {
/** /**
* Comma-separated list of origins to allow. '*' allows all origins. When not set, * Comma-separated list of origins to allow. '*' allows all origins. When credentials
* CORS support is disabled. When credentials are supported only explicit urls are * are allowed, '*' cannot be used and origin patterns should be configured instead.
* allowed. * When no allowed origins or allowed origin patterns are set, CORS support is
* disabled.
*/ */
private List<String> allowedOrigins = new ArrayList<>(); private List<String> allowedOrigins = new ArrayList<>();
/** /**
* Comma-separated list of origins patterns to allow. Must be used when credentials * Comma-separated list of origin patterns to allow. Unlike allowed origins which only
* are supported and do you want to use wildcard urls. * supports '*', origin patterns are more flexible (for example
* 'https://*.example.com') and can be used when credentials are allowed. When no
* allowed origin patterns or allowed origins are set, CORS support is disabled.
*/ */
private List<String> allowedOriginPatterns = new ArrayList<>(); private List<String> allowedOriginPatterns = new ArrayList<>();

View File

@ -1,5 +1,5 @@
/* /*
* Copyright 2012-2020 the original author or authors. * Copyright 2012-2021 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -16,9 +16,6 @@
package org.springframework.boot.actuate.autoconfigure.integrationtest; package org.springframework.boot.actuate.autoconfigure.integrationtest;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.options;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import java.util.function.Consumer; import java.util.function.Consumer;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
@ -74,6 +71,19 @@ class WebFluxEndpointCorsIntegrationTests {
})); }));
} }
@Test
void settingAllowedOriginPatternsEnablesCors() {
this.contextRunner
.withPropertyValues("management.endpoints.web.cors.allowed-origin-patterns:*.example.org",
"management.endpoints.web.cors.allow-credentials:true")
.run(withWebTestClient((webTestClient) -> {
webTestClient.options().uri("/actuator/beans").header("Origin", "spring.example.com")
.header(HttpHeaders.ACCESS_CONTROL_REQUEST_METHOD, "GET").exchange().expectStatus()
.isForbidden();
performAcceptedCorsRequest(webTestClient, "/actuator/beans");
}));
}
@Test @Test
void maxAgeDefaultsTo30Minutes() { void maxAgeDefaultsTo30Minutes() {
this.contextRunner.withPropertyValues("management.endpoints.web.cors.allowed-origins:spring.example.org") this.contextRunner.withPropertyValues("management.endpoints.web.cors.allowed-origins:spring.example.org")
@ -148,29 +158,6 @@ class WebFluxEndpointCorsIntegrationTests {
.expectHeader().doesNotExist(HttpHeaders.ACCESS_CONTROL_ALLOW_CREDENTIALS))); .expectHeader().doesNotExist(HttpHeaders.ACCESS_CONTROL_ALLOW_CREDENTIALS)));
} }
@Test
void settingAllowedOriginsPattern() {
this.contextRunner
.withPropertyValues("management.endpoints.web.cors.allowed-origin-patterns:*.example.com",
"management.endpoints.web.cors.allow-credentials:true")
.run(withWebTestClient((webTestClient) -> webTestClient.options().uri("/actuator/beans")
.header("Origin", "spring.example.com")
.header(HttpHeaders.ACCESS_CONTROL_REQUEST_METHOD, "HEAD").exchange().expectStatus().isOk()
.expectHeader().valueEquals(HttpHeaders.ACCESS_CONTROL_ALLOW_METHODS, "GET,HEAD")));
}
@Test
void requestsWithDisallowedOriginPatternsAreRejected() {
this.contextRunner
.withPropertyValues("management.endpoints.web.cors.allowed-origin-patterns:*.example.com",
"management.endpoints.web.cors.allow-credentials:true")
.run(withWebTestClient((webTestClient) -> webTestClient.options().uri("/actuator/beans")
.header("Origin", "spring.example.org")
.header(HttpHeaders.ACCESS_CONTROL_REQUEST_METHOD, "HEAD").exchange().expectStatus()
.isForbidden()));
}
private ContextConsumer<ReactiveWebApplicationContext> withWebTestClient(Consumer<WebTestClient> webTestClient) { private ContextConsumer<ReactiveWebApplicationContext> withWebTestClient(Consumer<WebTestClient> webTestClient) {
return (context) -> webTestClient.accept(WebTestClient.bindToApplicationContext(context).configureClient() return (context) -> webTestClient.accept(WebTestClient.bindToApplicationContext(context).configureClient()
.baseUrl("https://spring.example.org").build()); .baseUrl("https://spring.example.org").build());

View File

@ -1,5 +1,5 @@
/* /*
* Copyright 2012-2019 the original author or authors. * Copyright 2012-2021 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -77,6 +77,17 @@ class WebMvcEndpointCorsIntegrationTests {
})); }));
} }
@Test
void settingAllowedOriginPatternsEnablesCors() {
this.contextRunner.withPropertyValues("management.endpoints.web.cors.allowed-origin-patterns:*.example.com",
"management.endpoints.web.cors.allow-credentials:true").run(withMockMvc((mockMvc) -> {
mockMvc.perform(options("/actuator/beans").header("Origin", "bar.example.org")
.header(HttpHeaders.ACCESS_CONTROL_REQUEST_METHOD, "GET"))
.andExpect(status().isForbidden());
performAcceptedCorsRequest(mockMvc);
}));
}
@Test @Test
void maxAgeDefaultsTo30Minutes() { void maxAgeDefaultsTo30Minutes() {
this.contextRunner.withPropertyValues("management.endpoints.web.cors.allowed-origins:foo.example.com") this.contextRunner.withPropertyValues("management.endpoints.web.cors.allowed-origins:foo.example.com")
@ -156,27 +167,6 @@ class WebMvcEndpointCorsIntegrationTests {
.andExpect(header().doesNotExist(HttpHeaders.ACCESS_CONTROL_ALLOW_CREDENTIALS)))); .andExpect(header().doesNotExist(HttpHeaders.ACCESS_CONTROL_ALLOW_CREDENTIALS))));
} }
@Test
void settingAllowedOriginsPattern() {
this.contextRunner.withPropertyValues("management.endpoints.web.cors.allowed-origin-patterns:*.example.com",
"management.endpoints.web.cors.allow-credentials:true").run(withMockMvc((mockMvc) -> {
mockMvc.perform(options("/actuator/beans").header("Origin", "bar.example.com")
.header(HttpHeaders.ACCESS_CONTROL_REQUEST_METHOD, "GET")).andExpect(status().isOk());
performAcceptedCorsRequest(mockMvc);
}));
}
@Test
void requestsWithDisallowedOriginPatternsAreRejected() {
this.contextRunner.withPropertyValues("management.endpoints.web.cors.allowed-origin-patterns:*.example.com",
"management.endpoints.web.cors.allow-credentials:true").run(withMockMvc((mockMvc) -> {
mockMvc.perform(options("/actuator/beans").header("Origin", "bar.domain.com")
.header(HttpHeaders.ACCESS_CONTROL_REQUEST_METHOD, "GET"))
.andExpect(status().isForbidden());
performAcceptedCorsRequest(mockMvc);
}));
}
private ContextConsumer<WebApplicationContext> withMockMvc(MockMvcConsumer mockMvc) { private ContextConsumer<WebApplicationContext> withMockMvc(MockMvcConsumer mockMvc) {
return (context) -> mockMvc.accept(MockMvcBuilders.webAppContextSetup(context).build()); return (context) -> mockMvc.accept(MockMvcBuilders.webAppContextSetup(context).build());
} }