diff --git a/docs/src/main/asciidoc/spring-cloud-gateway.adoc b/docs/src/main/asciidoc/spring-cloud-gateway.adoc index 0d739bce..53816cd7 100644 --- a/docs/src/main/asciidoc/spring-cloud-gateway.adoc +++ b/docs/src/main/asciidoc/spring-cloud-gateway.adoc @@ -1638,6 +1638,8 @@ spring: In the example above, CORS requests will be allowed from requests that originate from docs.spring.io for all GET requested paths. +To provide the same CORS configuration to requests that are not handled by some gateway route predicate, set the property `spring.cloud.gateway.globalcors.add-to-simple-url-handler-mapping` equal to true. This is useful when trying to support CORS preflight requests and your route predicate doesn't evalute to true because the http method is `options`. + == Actuator API The `/gateway` actuator endpoint allows to monitor and interact with a Spring Cloud Gateway application. To be remotely accessible, the endpoint has to be https://docs.spring.io/spring-boot/docs/current/reference/html/production-ready-endpoints.html#production-ready-endpoints-enabling-endpoints[enabled] and https://docs.spring.io/spring-boot/docs/current/reference/html/production-ready-endpoints.html#production-ready-endpoints-exposing-endpoints[exposed via HTTP or JMX] in the application properties. diff --git a/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/config/SimpleUrlHandlerMappingGlobalCorsAutoConfiguration.java b/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/config/SimpleUrlHandlerMappingGlobalCorsAutoConfiguration.java new file mode 100644 index 00000000..84d18fb4 --- /dev/null +++ b/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/config/SimpleUrlHandlerMappingGlobalCorsAutoConfiguration.java @@ -0,0 +1,50 @@ +/* + * Copyright 2013-2018 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 + * + * https://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.cloud.gateway.config; + +import javax.annotation.PostConstruct; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.reactive.handler.SimpleUrlHandlerMapping; + +/** + * This is useful for PreFlight CORS requests. We can add a "global" configuration here so + * we don't have to modify existing predicates to allow the "options" HTTP method. + * + */ + +@Configuration +@ConditionalOnClass(SimpleUrlHandlerMapping.class) +@ConditionalOnProperty(name = "spring.cloud.gateway.globalcors.add-to-simple-url-handler-mapping", matchIfMissing = false) +public class SimpleUrlHandlerMappingGlobalCorsAutoConfiguration { + + @Autowired + private GlobalCorsProperties globalCorsProperties; + + @Autowired + private SimpleUrlHandlerMapping simpleUrlHandlerMapping; + + @PostConstruct + void config() { + simpleUrlHandlerMapping + .setCorsConfigurations(globalCorsProperties.getCorsConfigurations()); + } + +} diff --git a/spring-cloud-gateway-core/src/main/resources/META-INF/spring.factories b/spring-cloud-gateway-core/src/main/resources/META-INF/spring.factories index 733183a5..4765e647 100644 --- a/spring-cloud-gateway-core/src/main/resources/META-INF/spring.factories +++ b/spring-cloud-gateway-core/src/main/resources/META-INF/spring.factories @@ -6,6 +6,8 @@ org.springframework.cloud.gateway.config.GatewayLoadBalancerClientAutoConfigurat org.springframework.cloud.gateway.config.GatewayNoLoadBalancerClientAutoConfiguration,\ org.springframework.cloud.gateway.config.GatewayMetricsAutoConfiguration,\ org.springframework.cloud.gateway.config.GatewayRedisAutoConfiguration,\ -org.springframework.cloud.gateway.discovery.GatewayDiscoveryClientAutoConfiguration +org.springframework.cloud.gateway.discovery.GatewayDiscoveryClientAutoConfiguration,\ +org.springframework.cloud.gateway.config.SimpleUrlHandlerMappingGlobalCorsAutoConfiguration + org.springframework.boot.env.EnvironmentPostProcessor=\ org.springframework.cloud.gateway.config.GatewayEnvironmentPostProcessor \ No newline at end of file diff --git a/spring-cloud-gateway-core/src/test/java/org/springframework/cloud/gateway/cors/SimpleUrlHandlerCorsTests.java b/spring-cloud-gateway-core/src/test/java/org/springframework/cloud/gateway/cors/SimpleUrlHandlerCorsTests.java new file mode 100644 index 00000000..f909c7f2 --- /dev/null +++ b/spring-cloud-gateway-core/src/test/java/org/springframework/cloud/gateway/cors/SimpleUrlHandlerCorsTests.java @@ -0,0 +1,94 @@ +/* + * Copyright 2013-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 + * + * https://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.cloud.gateway.cors; + +import java.util.Arrays; + +import org.junit.Test; +import org.junit.runner.RunWith; +import reactor.core.publisher.Mono; + +import org.springframework.boot.SpringBootConfiguration; +import org.springframework.boot.autoconfigure.AutoConfigureBefore; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.cloud.gateway.config.GatewayAutoConfiguration; +import org.springframework.cloud.gateway.test.BaseWebClientTests; +import org.springframework.context.annotation.Import; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpMethod; +import org.springframework.http.HttpStatus; +import org.springframework.test.annotation.DirtiesContext; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.context.junit4.SpringRunner; +import org.springframework.web.reactive.function.client.ClientResponse; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT; + +@RunWith(SpringRunner.class) +@SpringBootTest(webEnvironment = RANDOM_PORT, properties = "spring.cloud.gateway.globalcors.add-to-simple-url-handler-mapping=true") +@DirtiesContext +@ActiveProfiles("request-header-web-filter") +public class SimpleUrlHandlerCorsTests extends BaseWebClientTests { + + @Test + public void testPreFlightCorsRequestNotHandledByGW() { + ClientResponse clientResponse = webClient.options().uri("/abc/123/function") + .header("Origin", "domain.com") + .header("Access-Control-Request-Method", "GET").exchange().block(); + HttpHeaders asHttpHeaders = clientResponse.headers().asHttpHeaders(); + Mono bodyToMono = clientResponse.bodyToMono(String.class); + // pre-flight request shouldn't return the response body + assertThat(bodyToMono.block()).isNull(); + assertThat(asHttpHeaders.getAccessControlAllowOrigin()) + .as("Missing header value in response: " + + HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN) + .isEqualTo("*"); + assertThat(asHttpHeaders.getAccessControlAllowMethods()) + .as("Missing header value in response: " + + HttpHeaders.ACCESS_CONTROL_ALLOW_METHODS) + .isEqualTo(Arrays.asList(new HttpMethod[] { HttpMethod.GET })); + assertThat(clientResponse.statusCode()).as("Pre Flight call failed.") + .isEqualTo(HttpStatus.OK); + } + + @Test + public void testCorsRequestNotHandledByGW() { + ClientResponse clientResponse = webClient.get().uri("/abc/123/function") + .header("Origin", "domain.com").header(HttpHeaders.HOST, "www.path.org") + .exchange().block(); + HttpHeaders asHttpHeaders = clientResponse.headers().asHttpHeaders(); + Mono bodyToMono = clientResponse.bodyToMono(String.class); + assertThat(bodyToMono.block()).isNotNull(); + assertThat(asHttpHeaders.getAccessControlAllowOrigin()) + .as("Missing header value in response: " + + HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN) + .isEqualTo("*"); + assertThat(clientResponse.statusCode()).as("CORS request failed.") + .isEqualTo(HttpStatus.NOT_FOUND); + } + + @EnableAutoConfiguration + @SpringBootConfiguration + @AutoConfigureBefore(GatewayAutoConfiguration.class) + @Import(DefaultTestConfig.class) + public static class TestConfig { + + } + +}