From 83515df63b676387754fdbd9884bb4f78dcb724a Mon Sep 17 00:00:00 2001 From: Tony Clarke Date: Mon, 27 Aug 2018 13:26:22 -0400 Subject: [PATCH 1/2] Adds conifugration to allow http options globally. This is done by adding a global cors config to simlple URL Handler mapping. Spring webflux adds a static resource handler, WebFluxAutoConfiguration.WebFluxConfig. This adds a SimpleURLHandler which will be the handler that ends returning http 404 for requets that match any spring cloud gateway predicates. fixes gh-840 --- .../main/asciidoc/spring-cloud-gateway.adoc | 2 + ...lerMappingGlobalCorsAutoConfiguration.java | 50 ++++++++++ .../main/resources/META-INF/spring.factories | 4 +- .../cors/SimpleUrlHandlerCorsTests.java | 92 +++++++++++++++++++ 4 files changed, 147 insertions(+), 1 deletion(-) create mode 100644 spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/config/SimpleUrlHandlerMappingGlobalCorsAutoConfiguration.java create mode 100644 spring-cloud-gateway-core/src/test/java/org/springframework/cloud/gateway/cors/SimpleUrlHandlerCorsTests.java diff --git a/docs/src/main/asciidoc/spring-cloud-gateway.adoc b/docs/src/main/asciidoc/spring-cloud-gateway.adoc index e4d5d450..2ffeb37e 100644 --- a/docs/src/main/asciidoc/spring-cloud-gateway.adoc +++ b/docs/src/main/asciidoc/spring-cloud-gateway.adoc @@ -1556,6 +1556,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..524cd87d --- /dev/null +++ b/spring-cloud-gateway-core/src/test/java/org/springframework/cloud/gateway/cors/SimpleUrlHandlerCorsTests.java @@ -0,0 +1,92 @@ +/* + * 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 { + + } + +} From afa858af6ef007dcd5dc55ed4fff958e9d3c7f78 Mon Sep 17 00:00:00 2001 From: Spencer Gibb Date: Thu, 25 Jul 2019 15:27:35 -0400 Subject: [PATCH 2/2] formatting --- .../cloud/gateway/cors/SimpleUrlHandlerCorsTests.java | 8 +++++--- .../RetryGatewayFilterFactoryIntegrationTests.java | 10 ++++------ .../gateway/mvc/ProductionConfigurationTests.java | 3 +-- 3 files changed, 10 insertions(+), 11 deletions(-) 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 index 524cd87d..f909c7f2 100644 --- 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 @@ -57,11 +57,12 @@ public class SimpleUrlHandlerCorsTests extends BaseWebClientTests { assertThat(bodyToMono.block()).isNull(); assertThat(asHttpHeaders.getAccessControlAllowOrigin()) .as("Missing header value in response: " - + HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN).isEqualTo("*"); + + 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})); + .isEqualTo(Arrays.asList(new HttpMethod[] { HttpMethod.GET })); assertThat(clientResponse.statusCode()).as("Pre Flight call failed.") .isEqualTo(HttpStatus.OK); } @@ -76,7 +77,8 @@ public class SimpleUrlHandlerCorsTests extends BaseWebClientTests { assertThat(bodyToMono.block()).isNotNull(); assertThat(asHttpHeaders.getAccessControlAllowOrigin()) .as("Missing header value in response: " - + HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN).isEqualTo("*"); + + HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN) + .isEqualTo("*"); assertThat(clientResponse.statusCode()).as("CORS request failed.") .isEqualTo(HttpStatus.NOT_FOUND); } diff --git a/spring-cloud-gateway-core/src/test/java/org/springframework/cloud/gateway/filter/factory/RetryGatewayFilterFactoryIntegrationTests.java b/spring-cloud-gateway-core/src/test/java/org/springframework/cloud/gateway/filter/factory/RetryGatewayFilterFactoryIntegrationTests.java index d5349c3b..39a8fe14 100644 --- a/spring-cloud-gateway-core/src/test/java/org/springframework/cloud/gateway/filter/factory/RetryGatewayFilterFactoryIntegrationTests.java +++ b/spring-cloud-gateway-core/src/test/java/org/springframework/cloud/gateway/filter/factory/RetryGatewayFilterFactoryIntegrationTests.java @@ -208,12 +208,10 @@ public class RetryGatewayFilterFactoryIntegrationTests extends BaseWebClientTest .uri(uri)) .route("retry_with_backoff", r -> r.host("**.retrywithbackoff.org") - .filters(f -> f.prefixPath("/httpbin") - .retry(config -> { - config.setRetries(2).setBackoff( - Duration.ofMillis(100), null, 2, true); - })) - .uri(uri)) + .filters(f -> f.prefixPath("/httpbin").retry(config -> { + config.setRetries(2).setBackoff(Duration.ofMillis(100), + null, 2, true); + })).uri(uri)) .route("retry_with_loadbalancer", r -> r.host("**.retrywithloadbalancer.org") diff --git a/spring-cloud-gateway-mvc/src/test/java/org/springframework/cloud/gateway/mvc/ProductionConfigurationTests.java b/spring-cloud-gateway-mvc/src/test/java/org/springframework/cloud/gateway/mvc/ProductionConfigurationTests.java index 93c49651..18ecf85e 100644 --- a/spring-cloud-gateway-mvc/src/test/java/org/springframework/cloud/gateway/mvc/ProductionConfigurationTests.java +++ b/spring-cloud-gateway-mvc/src/test/java/org/springframework/cloud/gateway/mvc/ProductionConfigurationTests.java @@ -359,8 +359,7 @@ public class ProductionConfigurationTests { @PostMapping("/proxy/no-body") public ResponseEntity noBody(ProxyExchange proxy) throws Exception { - return proxy.uri(home.toString() + "/foos") - .post(); + return proxy.uri(home.toString() + "/foos").post(); } @GetMapping("/forward/**")