parent
deb16e1617
commit
5f3c2bef50
|
|
@ -17,17 +17,24 @@
|
|||
package org.springframework.boot.actuate.autoconfigure.endpoint.web.reactive;
|
||||
|
||||
import org.springframework.boot.actuate.autoconfigure.endpoint.web.WebEndpointProperties;
|
||||
import org.springframework.boot.actuate.autoconfigure.endpoint.web.servlet.CorsEndpointProperties;
|
||||
import org.springframework.boot.actuate.autoconfigure.web.ManagementContextConfiguration;
|
||||
import org.springframework.boot.actuate.endpoint.annotation.Endpoint;
|
||||
import org.springframework.boot.actuate.endpoint.web.EndpointMediaTypes;
|
||||
import org.springframework.boot.actuate.endpoint.web.annotation.WebAnnotationEndpointDiscoverer;
|
||||
import org.springframework.boot.actuate.endpoint.web.reactive.WebFluxEndpointHandlerMapping;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication.Type;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.boot.endpoint.web.EndpointMapping;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.http.server.reactive.HttpHandler;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
import org.springframework.web.cors.CorsConfiguration;
|
||||
import org.springframework.web.reactive.DispatcherHandler;
|
||||
|
||||
/**
|
||||
* {@link ManagementContextConfiguration} for Reactive {@link Endpoint} concerns.
|
||||
|
|
@ -38,18 +45,44 @@ import org.springframework.context.annotation.Bean;
|
|||
*/
|
||||
@ManagementContextConfiguration
|
||||
@ConditionalOnWebApplication(type = Type.REACTIVE)
|
||||
@ConditionalOnClass({ DispatcherHandler.class, HttpHandler.class })
|
||||
@ConditionalOnBean(WebAnnotationEndpointDiscoverer.class)
|
||||
@EnableConfigurationProperties(CorsEndpointProperties.class)
|
||||
public class WebFluxEndpointManagementContextConfiguration {
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
public WebFluxEndpointHandlerMapping webEndpointReactiveHandlerMapping(
|
||||
WebAnnotationEndpointDiscoverer endpointDiscoverer,
|
||||
EndpointMediaTypes endpointMediaTypes,
|
||||
EndpointMediaTypes endpointMediaTypes, CorsEndpointProperties corsProperties,
|
||||
WebEndpointProperties webEndpointProperties) {
|
||||
return new WebFluxEndpointHandlerMapping(
|
||||
new EndpointMapping(webEndpointProperties.getBasePath()),
|
||||
endpointDiscoverer.discoverEndpoints(), endpointMediaTypes);
|
||||
endpointDiscoverer.discoverEndpoints(), endpointMediaTypes, getCorsConfiguration(corsProperties));
|
||||
}
|
||||
|
||||
private CorsConfiguration getCorsConfiguration(CorsEndpointProperties properties) {
|
||||
if (CollectionUtils.isEmpty(properties.getAllowedOrigins())) {
|
||||
return null;
|
||||
}
|
||||
CorsConfiguration configuration = new CorsConfiguration();
|
||||
configuration.setAllowedOrigins(properties.getAllowedOrigins());
|
||||
if (!CollectionUtils.isEmpty(properties.getAllowedHeaders())) {
|
||||
configuration.setAllowedHeaders(properties.getAllowedHeaders());
|
||||
}
|
||||
if (!CollectionUtils.isEmpty(properties.getAllowedMethods())) {
|
||||
configuration.setAllowedMethods(properties.getAllowedMethods());
|
||||
}
|
||||
if (!CollectionUtils.isEmpty(properties.getExposedHeaders())) {
|
||||
configuration.setExposedHeaders(properties.getExposedHeaders());
|
||||
}
|
||||
if (properties.getMaxAge() != null) {
|
||||
configuration.setMaxAge(properties.getMaxAge().getSeconds());
|
||||
}
|
||||
if (properties.getAllowCredentials() != null) {
|
||||
configuration.setAllowCredentials(properties.getAllowCredentials());
|
||||
}
|
||||
return configuration;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,203 @@
|
|||
/*
|
||||
* Copyright 2012-2017 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.actuate.autoconfigure.integrationtest;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import org.springframework.boot.actuate.autoconfigure.beans.BeansEndpointAutoConfiguration;
|
||||
import org.springframework.boot.actuate.autoconfigure.endpoint.EndpointAutoConfiguration;
|
||||
import org.springframework.boot.actuate.autoconfigure.endpoint.web.WebEndpointAutoConfiguration;
|
||||
import org.springframework.boot.actuate.autoconfigure.endpoint.web.reactive.WebFluxEndpointManagementContextConfiguration;
|
||||
import org.springframework.boot.actuate.autoconfigure.web.reactive.ReactiveManagementContextAutoConfiguration;
|
||||
import org.springframework.boot.actuate.autoconfigure.web.server.ManagementContextAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionEvaluationReport;
|
||||
import org.springframework.boot.autoconfigure.http.codec.CodecsAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.logging.ConditionEvaluationReportMessage;
|
||||
import org.springframework.boot.autoconfigure.web.reactive.HttpHandlerAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.web.reactive.WebFluxAutoConfiguration;
|
||||
import org.springframework.boot.test.util.TestPropertyValues;
|
||||
import org.springframework.boot.web.reactive.context.AnnotationConfigReactiveWebApplicationContext;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.test.web.reactive.server.WebTestClient;
|
||||
|
||||
/**
|
||||
* Integration tests for the WebFlux actuator endpoints' CORS support
|
||||
*
|
||||
* @author Brian Clozel
|
||||
* @see WebFluxEndpointManagementContextConfiguration
|
||||
*/
|
||||
public class WebFluxEndpointCorsIntegrationTests {
|
||||
|
||||
private AnnotationConfigReactiveWebApplicationContext context;
|
||||
|
||||
@Before
|
||||
public void createContext() {
|
||||
this.context = new AnnotationConfigReactiveWebApplicationContext();
|
||||
this.context.register(JacksonAutoConfiguration.class,
|
||||
CodecsAutoConfiguration.class,
|
||||
WebFluxAutoConfiguration.class, HttpHandlerAutoConfiguration.class,
|
||||
EndpointAutoConfiguration.class, WebEndpointAutoConfiguration.class,
|
||||
ManagementContextAutoConfiguration.class,
|
||||
ReactiveManagementContextAutoConfiguration.class,
|
||||
BeansEndpointAutoConfiguration.class);
|
||||
TestPropertyValues.of("management.endpoints.web.expose:*").applyTo(this.context);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void corsIsDisabledByDefault() throws Exception {
|
||||
WebTestClient client = createWebTestClient();
|
||||
System.out.println(new ConditionEvaluationReportMessage(
|
||||
this.context.getBean(ConditionEvaluationReport.class)));
|
||||
client.options().uri("/actuator/beans")
|
||||
.header("Origin", "spring.example.org")
|
||||
.header(HttpHeaders.ACCESS_CONTROL_REQUEST_METHOD, "GET")
|
||||
.exchange()
|
||||
.expectStatus().isForbidden();
|
||||
//TODO: .expectHeader().doesNotExist(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void settingAllowedOriginsEnablesCors() throws Exception {
|
||||
TestPropertyValues
|
||||
.of("management.endpoints.web.cors.allowed-origins:spring.example.org")
|
||||
.applyTo(this.context);
|
||||
createWebTestClient()
|
||||
.options().uri("/actuator/beans")
|
||||
.header("Origin", "test.example.org")
|
||||
.header(HttpHeaders.ACCESS_CONTROL_REQUEST_METHOD, "GET")
|
||||
.exchange()
|
||||
.expectStatus().isForbidden();
|
||||
performAcceptedCorsRequest("/actuator/beans");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void maxAgeDefaultsTo30Minutes() throws Exception {
|
||||
TestPropertyValues
|
||||
.of("management.endpoints.web.cors.allowed-origins:spring.example.org")
|
||||
.applyTo(this.context);
|
||||
performAcceptedCorsRequest("/actuator/beans")
|
||||
.expectHeader().valueEquals(HttpHeaders.ACCESS_CONTROL_MAX_AGE, "1800");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void maxAgeCanBeConfigured() throws Exception {
|
||||
TestPropertyValues
|
||||
.of("management.endpoints.web.cors.allowed-origins:spring.example.org",
|
||||
"management.endpoints.web.cors.max-age: 2400")
|
||||
.applyTo(this.context);
|
||||
performAcceptedCorsRequest("/actuator/beans")
|
||||
.expectHeader().valueEquals(HttpHeaders.ACCESS_CONTROL_MAX_AGE, "2400");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void requestsWithDisallowedHeadersAreRejected() throws Exception {
|
||||
TestPropertyValues
|
||||
.of("management.endpoints.web.cors.allowed-origins:spring.example.org")
|
||||
.applyTo(this.context);
|
||||
createWebTestClient()
|
||||
.options().uri("/actuator/beans")
|
||||
.header("Origin", "spring.example.org")
|
||||
.header(HttpHeaders.ACCESS_CONTROL_REQUEST_METHOD, "GET")
|
||||
.header(HttpHeaders.ACCESS_CONTROL_REQUEST_HEADERS, "Alpha")
|
||||
.exchange()
|
||||
.expectStatus().isForbidden();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void allowedHeadersCanBeConfigured() throws Exception {
|
||||
TestPropertyValues
|
||||
.of("management.endpoints.web.cors.allowed-origins:spring.example.org",
|
||||
"management.endpoints.web.cors.allowed-headers:Alpha,Bravo")
|
||||
.applyTo(this.context);
|
||||
createWebTestClient()
|
||||
.options().uri("/actuator/beans")
|
||||
.header("Origin", "spring.example.org")
|
||||
.header(HttpHeaders.ACCESS_CONTROL_REQUEST_METHOD, "GET")
|
||||
.header(HttpHeaders.ACCESS_CONTROL_REQUEST_HEADERS, "Alpha")
|
||||
.exchange()
|
||||
.expectStatus().isOk()
|
||||
.expectHeader().valueEquals(HttpHeaders.ACCESS_CONTROL_ALLOW_HEADERS, "Alpha");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void requestsWithDisallowedMethodsAreRejected() throws Exception {
|
||||
TestPropertyValues
|
||||
.of("management.endpoints.web.cors.allowed-origins:spring.example.org")
|
||||
.applyTo(this.context);
|
||||
createWebTestClient()
|
||||
.options().uri("/actuator/beans")
|
||||
.header("Origin", "spring.example.org")
|
||||
.header(HttpHeaders.ACCESS_CONTROL_REQUEST_METHOD, "PATCH")
|
||||
.exchange()
|
||||
.expectStatus().isForbidden();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void allowedMethodsCanBeConfigured() throws Exception {
|
||||
TestPropertyValues
|
||||
.of("management.endpoints.web.cors.allowed-origins:spring.example.org",
|
||||
"management.endpoints.web.cors.allowed-methods:GET,HEAD")
|
||||
.applyTo(this.context);
|
||||
createWebTestClient()
|
||||
.options().uri("/actuator/beans")
|
||||
.header("Origin", "spring.example.org")
|
||||
.header(HttpHeaders.ACCESS_CONTROL_REQUEST_METHOD, "HEAD")
|
||||
.exchange()
|
||||
.expectStatus().isOk()
|
||||
.expectHeader().valueEquals(HttpHeaders.ACCESS_CONTROL_ALLOW_METHODS, "GET,HEAD");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void credentialsCanBeAllowed() throws Exception {
|
||||
TestPropertyValues
|
||||
.of("management.endpoints.web.cors.allowed-origins:spring.example.org",
|
||||
"management.endpoints.web.cors.allow-credentials:true")
|
||||
.applyTo(this.context);
|
||||
performAcceptedCorsRequest("/actuator/beans")
|
||||
.expectHeader().valueEquals(HttpHeaders.ACCESS_CONTROL_ALLOW_CREDENTIALS, "true");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void credentialsCanBeDisabled() throws Exception {
|
||||
TestPropertyValues
|
||||
.of("management.endpoints.web.cors.allowed-origins:spring.example.org",
|
||||
"management.endpoints.web.cors.allow-credentials:false")
|
||||
.applyTo(this.context);
|
||||
performAcceptedCorsRequest("/actuator/beans");
|
||||
//TODO .expectHeader().doesNotExist(HttpHeaders.ACCESS_CONTROL_ALLOW_CREDENTIALS);
|
||||
}
|
||||
|
||||
|
||||
private WebTestClient createWebTestClient() {
|
||||
this.context.refresh();
|
||||
return WebTestClient.bindToApplicationContext(this.context)
|
||||
.configureClient().baseUrl("https://spring.example.org").build();
|
||||
}
|
||||
|
||||
private WebTestClient.ResponseSpec performAcceptedCorsRequest(String url) throws Exception {
|
||||
return createWebTestClient()
|
||||
.options().uri(url)
|
||||
.header(HttpHeaders.ORIGIN, "spring.example.org")
|
||||
.header(HttpHeaders.ACCESS_CONTROL_REQUEST_METHOD, "GET")
|
||||
.exchange()
|
||||
.expectHeader().valueEquals(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN, "spring.example.org")
|
||||
.expectStatus().isOk();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -25,7 +25,6 @@ import org.springframework.boot.actuate.autoconfigure.endpoint.web.WebEndpointAu
|
|||
import org.springframework.boot.actuate.autoconfigure.endpoint.web.servlet.WebMvcEndpointManagementContextConfiguration;
|
||||
import org.springframework.boot.actuate.autoconfigure.web.server.ManagementContextAutoConfiguration;
|
||||
import org.springframework.boot.actuate.autoconfigure.web.servlet.ServletManagementContextAutoConfiguration;
|
||||
import org.springframework.boot.actuate.beans.BeansEndpoint;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionEvaluationReport;
|
||||
import org.springframework.boot.autoconfigure.http.HttpMessageConvertersAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration;
|
||||
|
|
@ -33,13 +32,8 @@ import org.springframework.boot.autoconfigure.logging.ConditionEvaluationReportM
|
|||
import org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration;
|
||||
import org.springframework.boot.test.util.TestPropertyValues;
|
||||
import org.springframework.context.ConfigurableApplicationContext;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.mock.web.MockServletContext;
|
||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
|
||||
import org.springframework.test.web.servlet.MockMvc;
|
||||
import org.springframework.test.web.servlet.ResultActions;
|
||||
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
|
||||
|
|
@ -50,7 +44,7 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.
|
|||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
|
||||
|
||||
/**
|
||||
* Integration tests for the actuator endpoints' CORS support
|
||||
* Integration tests for the MVC actuator endpoints' CORS support
|
||||
*
|
||||
* @author Andy Wilkinson
|
||||
* @see WebMvcEndpointManagementContextConfiguration
|
||||
|
|
@ -204,25 +198,4 @@ public class WebMvcEndpointCorsIntegrationTests {
|
|||
.andExpect(status().isOk());
|
||||
}
|
||||
|
||||
@Configuration
|
||||
static class EndpointConfiguration {
|
||||
|
||||
@Bean
|
||||
public BeansEndpoint beansEndpoint(
|
||||
ConfigurableApplicationContext applicationContext) {
|
||||
return new BeansEndpoint(applicationContext);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration
|
||||
static class SecurityConfiguration extends WebSecurityConfigurerAdapter {
|
||||
|
||||
@Override
|
||||
protected void configure(HttpSecurity http) throws Exception {
|
||||
http.cors();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue