diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/ManagementWebSecurityAutoConfiguration.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/ManagementWebSecurityAutoConfiguration.java index 623a6edad7d..8314dc6d0eb 100644 --- a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/ManagementWebSecurityAutoConfiguration.java +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/ManagementWebSecurityAutoConfiguration.java @@ -224,7 +224,7 @@ public class ManagementWebSecurityAutoConfiguration { http.requestMatcher(matcher); // ... but permitAll() for the non-sensitive ones configurePermittedRequests(http.authorizeRequests()); - http.httpBasic().authenticationEntryPoint(entryPoint); + http.httpBasic().authenticationEntryPoint(entryPoint).and().cors(); // No cookies for management endpoints by default http.csrf().disable(); http.sessionManagement() diff --git a/spring-boot-samples/spring-boot-sample-actuator/pom.xml b/spring-boot-samples/spring-boot-sample-actuator/pom.xml index b4edb3e8660..70617a2f62d 100644 --- a/spring-boot-samples/spring-boot-sample-actuator/pom.xml +++ b/spring-boot-samples/spring-boot-sample-actuator/pom.xml @@ -37,6 +37,11 @@ spring-boot-starter-jdbc + + org.apache.httpcomponents + httpclient + runtime + com.h2database h2 diff --git a/spring-boot-samples/spring-boot-sample-actuator/src/test/java/sample/actuator/CorsSampleActuatorApplicationTests.java b/spring-boot-samples/spring-boot-sample-actuator/src/test/java/sample/actuator/CorsSampleActuatorApplicationTests.java new file mode 100644 index 00000000000..dffe86f1772 --- /dev/null +++ b/spring-boot-samples/spring-boot-sample-actuator/src/test/java/sample/actuator/CorsSampleActuatorApplicationTests.java @@ -0,0 +1,88 @@ +package sample.actuator; + +import java.net.URI; +import java.util.Map; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.web.client.LocalHostUriTemplateHandler; +import org.springframework.boot.test.web.client.TestRestTemplate; +import org.springframework.context.ApplicationContext; +import org.springframework.http.HttpStatus; +import org.springframework.http.RequestEntity; +import org.springframework.http.ResponseEntity; +import org.springframework.http.client.HttpComponentsClientHttpRequestFactory; +import org.springframework.test.annotation.DirtiesContext; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.context.junit4.SpringRunner; +import org.springframework.web.client.RestTemplate; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Integration test for cors preflight requests to management endpoints. + * + * @author Madhura Bhave + */ +@RunWith(SpringRunner.class) +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) +@DirtiesContext +@ActiveProfiles("cors") +public class CorsSampleActuatorApplicationTests { + + private TestRestTemplate testRestTemplate; + + @Autowired + ApplicationContext applicationContext; + + @Before + public void setUp() throws Exception { + RestTemplate restTemplate = new RestTemplate(); + LocalHostUriTemplateHandler handler = new LocalHostUriTemplateHandler( + this.applicationContext.getEnvironment(), "http"); + restTemplate.setUriTemplateHandler(handler); + restTemplate.setRequestFactory(new HttpComponentsClientHttpRequestFactory()); + this.testRestTemplate = new TestRestTemplate(restTemplate); + } + + @Test + public void sensitiveEndpointShouldReturnUnauthorized() throws Exception { + ResponseEntity entity = this.testRestTemplate.getForEntity("/env", Map.class); + assertThat(entity.getStatusCode()).isEqualTo(HttpStatus.UNAUTHORIZED); + } + + @Test + public void preflightRequestForInsensitiveShouldReturnOk() throws Exception { + RequestEntity healthRequest = RequestEntity.options(new URI("/health")) + .header("Origin","http://localhost:8080") + .header("Access-Control-Request-Method", "GET") + .build(); + ResponseEntity exchange = this.testRestTemplate.exchange(healthRequest, Map.class); + assertThat(exchange.getStatusCode()).isEqualTo(HttpStatus.OK); + } + + @Test + public void preflightRequestForSensitiveEndpointShouldReturnOk() throws Exception { + RequestEntity entity = RequestEntity.options(new URI("/env")) + .header("Origin","http://localhost:8080") + .header("Access-Control-Request-Method", "GET") + .build(); + ResponseEntity env = this.testRestTemplate.exchange(entity, Map.class); + assertThat(env.getStatusCode()).isEqualTo(HttpStatus.OK); + } + + @Test + public void preflightRequestWhenCorsConfigInvalidShouldReturnForbidden() throws Exception { + RequestEntity entity = RequestEntity.options(new URI("/health")) + .header("Origin","http://localhost:9095") + .header("Access-Control-Request-Method", "GET") + .build(); + ResponseEntity exchange = this.testRestTemplate.exchange(entity, byte[].class); + assertThat(exchange.getStatusCode()).isEqualTo(HttpStatus.FORBIDDEN); + } + +} diff --git a/spring-boot-samples/spring-boot-sample-actuator/src/test/resources/application-cors.properties b/spring-boot-samples/spring-boot-sample-actuator/src/test/resources/application-cors.properties new file mode 100644 index 00000000000..2bfb6b6cdbd --- /dev/null +++ b/spring-boot-samples/spring-boot-sample-actuator/src/test/resources/application-cors.properties @@ -0,0 +1,2 @@ +endpoints.cors.allowed-origins=http://localhost:8080 +endpoints.cors.allowed-methods=GET \ No newline at end of file