From 74ba9061d52c05652851d8e8269e667817372d50 Mon Sep 17 00:00:00 2001 From: Rossen Stoyanchev Date: Wed, 22 Sep 2021 13:41:39 +0100 Subject: [PATCH] Polishing CORS support --- .../graphql/boot/GraphQlCorsProperties.java | 46 +++++++++-------- .../boot/GraphQlWebFluxAutoConfiguration.java | 14 +++--- .../boot/GraphQlWebMvcAutoConfiguration.java | 14 +++--- .../src/docs/asciidoc/boot-starter.adoc | 50 +++++++++---------- 4 files changed, 65 insertions(+), 59 deletions(-) diff --git a/graphql-spring-boot-starter/src/main/java/org/springframework/graphql/boot/GraphQlCorsProperties.java b/graphql-spring-boot-starter/src/main/java/org/springframework/graphql/boot/GraphQlCorsProperties.java index 3540004b..a9579765 100644 --- a/graphql-spring-boot-starter/src/main/java/org/springframework/graphql/boot/GraphQlCorsProperties.java +++ b/graphql-spring-boot-starter/src/main/java/org/springframework/graphql/boot/GraphQlCorsProperties.java @@ -24,6 +24,7 @@ import java.util.List; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.boot.context.properties.PropertyMapper; import org.springframework.boot.convert.DurationUnit; +import org.springframework.lang.Nullable; import org.springframework.util.CollectionUtils; import org.springframework.web.cors.CorsConfiguration; @@ -37,29 +38,31 @@ import org.springframework.web.cors.CorsConfiguration; @ConfigurationProperties(prefix = "spring.graphql.cors") public class GraphQlCorsProperties { /** - * Comma-separated list of origins to allow. '*' allows all origins. When credentials - * are allowed, '*' cannot be used and origin patterns should be configured instead. - * When no allowed origins or allowed origin patterns are set, CORS support is - * disabled. + * Comma-separated list of origins to allow with '*' allowing all origins. + * When allow-credentials is enabled, '*' cannot be used, and setting + * origin patterns should be considered instead. + * When neither allowed origins nor allowed origin patterns are set, + * cross-origin requests are effectively disabled. */ private List allowedOrigins = new ArrayList<>(); /** - * Comma-separated list of origin patterns to allow. Unlike allowed origins which only - * 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. + * Comma-separated list of origin patterns to allow. Unlike allowed origins + * which only support '*', origin patterns are more flexible, e.g. + * 'https://*.example.com', and can be used with allow-credentials. + * When neither allowed origins nor allowed origin patterns are set, + * cross-origin requests are effectively disabled. */ private List allowedOriginPatterns = new ArrayList<>(); /** - * Comma-separated list of methods to allow. '*' allows all methods. When not set, - * defaults to GET. + * Comma-separated list of HTTP methods to allow. '*' allows all methods. + * When not set, defaults to GET. */ private List allowedMethods = new ArrayList<>(); /** - * Comma-separated list of headers to allow in a request. '*' allows all headers. + * Comma-separated list of HTTP headers to allow in a request. '*' allows all headers. */ private List allowedHeaders = new ArrayList<>(); @@ -71,6 +74,7 @@ public class GraphQlCorsProperties { /** * Whether credentials are supported. When not set, credentials are not supported. */ + @Nullable private Boolean allowCredentials; /** @@ -120,6 +124,7 @@ public class GraphQlCorsProperties { this.exposedHeaders = exposedHeaders; } + @Nullable public Boolean getAllowCredentials() { return this.allowCredentials; } @@ -136,19 +141,20 @@ public class GraphQlCorsProperties { this.maxAge = maxAge; } + @Nullable public CorsConfiguration toCorsConfiguration() { if (CollectionUtils.isEmpty(this.allowedOrigins) && CollectionUtils.isEmpty(this.allowedOriginPatterns)) { return null; } PropertyMapper map = PropertyMapper.get(); - CorsConfiguration configuration = new CorsConfiguration(); - map.from(this::getAllowedOrigins).to(configuration::setAllowedOrigins); - map.from(this::getAllowedOriginPatterns).to(configuration::setAllowedOriginPatterns); - map.from(this::getAllowedHeaders).whenNot(CollectionUtils::isEmpty).to(configuration::setAllowedHeaders); - map.from(this::getAllowedMethods).whenNot(CollectionUtils::isEmpty).to(configuration::setAllowedMethods); - map.from(this::getExposedHeaders).whenNot(CollectionUtils::isEmpty).to(configuration::setExposedHeaders); - map.from(this::getMaxAge).whenNonNull().as(Duration::getSeconds).to(configuration::setMaxAge); - map.from(this::getAllowCredentials).whenNonNull().to(configuration::setAllowCredentials); - return configuration; + CorsConfiguration config = new CorsConfiguration(); + map.from(this::getAllowedOrigins).to(config::setAllowedOrigins); + map.from(this::getAllowedOriginPatterns).to(config::setAllowedOriginPatterns); + map.from(this::getAllowedHeaders).whenNot(CollectionUtils::isEmpty).to(config::setAllowedHeaders); + map.from(this::getAllowedMethods).whenNot(CollectionUtils::isEmpty).to(config::setAllowedMethods); + map.from(this::getExposedHeaders).whenNot(CollectionUtils::isEmpty).to(config::setExposedHeaders); + map.from(this::getMaxAge).whenNonNull().as(Duration::getSeconds).to(config::setMaxAge); + map.from(this::getAllowCredentials).whenNonNull().to(config::setAllowCredentials); + return config; } } diff --git a/graphql-spring-boot-starter/src/main/java/org/springframework/graphql/boot/GraphQlWebFluxAutoConfiguration.java b/graphql-spring-boot-starter/src/main/java/org/springframework/graphql/boot/GraphQlWebFluxAutoConfiguration.java index 754fa262..7abd59c1 100644 --- a/graphql-spring-boot-starter/src/main/java/org/springframework/graphql/boot/GraphQlWebFluxAutoConfiguration.java +++ b/graphql-spring-boot-starter/src/main/java/org/springframework/graphql/boot/GraphQlWebFluxAutoConfiguration.java @@ -155,20 +155,20 @@ public class GraphQlWebFluxAutoConfiguration { @Configuration(proxyBeanMethods = false) public static class GraphQlEndpointCorsConfiguration implements WebFluxConfigurer { - final GraphQlProperties graphql; + final GraphQlProperties graphQlProperties; - final GraphQlCorsProperties cors; + final GraphQlCorsProperties corsProperties; - public GraphQlEndpointCorsConfiguration(GraphQlProperties graphql, GraphQlCorsProperties cors) { - this.graphql = graphql; - this.cors = cors; + public GraphQlEndpointCorsConfiguration(GraphQlProperties graphQlProps, GraphQlCorsProperties corsProps) { + this.graphQlProperties = graphQlProps; + this.corsProperties = corsProps; } @Override public void addCorsMappings(CorsRegistry registry) { - CorsConfiguration configuration = cors.toCorsConfiguration(); + CorsConfiguration configuration = this.corsProperties.toCorsConfiguration(); if (configuration != null) { - registry.addMapping(graphql.getPath()).combine(configuration); + registry.addMapping(this.graphQlProperties.getPath()).combine(configuration); } } } diff --git a/graphql-spring-boot-starter/src/main/java/org/springframework/graphql/boot/GraphQlWebMvcAutoConfiguration.java b/graphql-spring-boot-starter/src/main/java/org/springframework/graphql/boot/GraphQlWebMvcAutoConfiguration.java index 1cb641ef..11f07a31 100644 --- a/graphql-spring-boot-starter/src/main/java/org/springframework/graphql/boot/GraphQlWebMvcAutoConfiguration.java +++ b/graphql-spring-boot-starter/src/main/java/org/springframework/graphql/boot/GraphQlWebMvcAutoConfiguration.java @@ -176,20 +176,20 @@ public class GraphQlWebMvcAutoConfiguration { @Configuration(proxyBeanMethods = false) public static class GraphQlEndpointCorsConfiguration implements WebMvcConfigurer { - final GraphQlProperties graphql; + final GraphQlProperties graphQlProperties; - final GraphQlCorsProperties cors; + final GraphQlCorsProperties corsProperties; - public GraphQlEndpointCorsConfiguration(GraphQlProperties graphql, GraphQlCorsProperties cors) { - this.graphql = graphql; - this.cors = cors; + public GraphQlEndpointCorsConfiguration(GraphQlProperties graphQlProps, GraphQlCorsProperties corsProps) { + this.graphQlProperties = graphQlProps; + this.corsProperties = corsProps; } @Override public void addCorsMappings(CorsRegistry registry) { - CorsConfiguration configuration = cors.toCorsConfiguration(); + CorsConfiguration configuration = this.corsProperties.toCorsConfiguration(); if (configuration != null) { - registry.addMapping(graphql.getPath()).combine(configuration); + registry.addMapping(this.graphQlProperties.getPath()).combine(configuration); } } diff --git a/spring-graphql-docs/src/docs/asciidoc/boot-starter.adoc b/spring-graphql-docs/src/docs/asciidoc/boot-starter.adoc index 55a86a9c..beb9e7d7 100644 --- a/spring-graphql-docs/src/docs/asciidoc/boot-starter.adoc +++ b/spring-graphql-docs/src/docs/asciidoc/boot-starter.adoc @@ -172,9 +172,33 @@ Declare a `ThreadLocalAccessor` bean to assist with the propagation of `ThreadLo values of interest in <>. +[[boot-graphql-cors]] +== CORS + +{spring-framework-ref-docs}/web.html#mvc-cors[Spring MVC] and +{spring-framework-ref-docs}/web-reactive.html#webflux-cors[Spring WebFlux] support CORS +(Cross-Origin Resource Sharing) requests. CORS is a critical part of the web config for +GraphQL applications that are accessed from browsers using different domains. + +The Boot starter supports the following CORS properties: + +[source,properties,indent=0,subs="verbatim"] +---- +spring.graphql.cors.allowed-origins=https://example.org # Comma-separated list of origins to allow. '*' allows all origins. +spring.graphql.cors.allowed-origin-patterns= # Comma-separated list of origin patterns like 'https://*.example.com' to allow. +spring.graphql.cors.allowed-methods=GET,POST # Comma-separated list of methods to allow. '*' allows all methods. +spring.graphql.cors.allowed-headers= # Comma-separated list of headers to allow in a request. '*' allows all headers. +spring.graphql.cors.exposed-headers= # Comma-separated list of headers to include in a response. +spring.graphql.cors.allow-credentials= # Whether credentials are supported. When not set, credentials are not supported. +spring.graphql.cors.max-age=1800s # How long the response from a pre-flight request can be cached by clients. +---- + +TIP: For more information about the properties and their meaning, check out the +{javadoc}/org/springframework/graphql/boot/GraphQlCorsProperties.html[GraphQlCorsProperties Javadoc]. + [[boot-graphql-exception-handling]] -== Exception +== Exceptions Spring GraphQL enables applications to register one or more Spring `DataFetcherExceptionResolver` components that are invoked sequentially until one @@ -232,30 +256,6 @@ spring.graphql.graphiql.path=/graphiql ---- -[[boot-graphql-cors]] -== CORS configuration - -Spring web frameworks all support CORS (Cross-Origin Resource Sharing), which is a critical part -of your web configuration if your GraphQL API is meant to be accessed by browsers using different domains. - -You can configure CORS support with properties: - -[source,properties,indent=0,subs="verbatim"] ----- -spring.graphql.cors.allowed-origins=https://example.org # Comma-separated list of origins to allow. '*' allows all origins. -spring.graphql.cors.allowed-origin-patterns= # Comma-separated list of origin patterns like 'https://*.example.com' to allow. -spring.graphql.cors.allowed-methods=GET,POST # Comma-separated list of methods to allow. '*' allows all methods. -spring.graphql.cors.allowed-headers= # Comma-separated list of headers to allow in a request. '*' allows all headers. -spring.graphql.cors.exposed-headers= # Comma-separated list of headers to include in a response. -spring.graphql.cors.allow-credentials= # Whether credentials are supported. When not set, credentials are not supported. -spring.graphql.cors.max-age=1800s # How long the response from a pre-flight request can be cached by clients. ----- - -TIP: For more information about the properties and their meaning, check out the {javadoc}/org/springframework/graphql/boot/GraphQlCorsProperties.html[GraphQlCorsProperties Javadoc]. - -You can also learn more about CORS and Spring support in {spring-framework-ref-docs}/web.html#mvc-cors[Spring MVC] and -{spring-framework-ref-docs}/web-reactive.html#webflux-cors[Spring WebFlux]. - [[boot-graphql-metrics]] == Metrics