Updates to CORS patterns contribution

Closes gh-25016
This commit is contained in:
Rossen Stoyanchev
2020-07-08 13:18:11 +03:00
parent 1181bb1852
commit 0e4e25d227
24 changed files with 488 additions and 256 deletions

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2018 the original author or authors.
* Copyright 2002-2020 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.
@@ -18,6 +18,7 @@ package org.springframework.web.reactive.config;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import org.springframework.web.cors.CorsConfiguration;
@@ -44,24 +45,28 @@ public class CorsRegistration {
/**
* The list of allowed origins that be specific origins, e.g.
* {@code "https://domain1.com"}, or {@code "*"} for all origins.
* <p>A matched origin is listed in the {@code Access-Control-Allow-Origin}
* response header of preflight actual CORS requests.
* <p>By default all origins are allowed.
* <p><strong>Note:</strong> CORS checks use values from "Forwarded"
* (<a href="https://tools.ietf.org/html/rfc7239">RFC 7239</a>),
* "X-Forwarded-Host", "X-Forwarded-Port", and "X-Forwarded-Proto" headers,
* if present, in order to reflect the client-originated address.
* Consider using the {@code ForwardedHeaderFilter} in order to choose from a
* central place whether to extract and use, or to discard such headers.
* See the Spring Framework reference for more on this filter.
* A list of origins for which cross-origin requests are allowed. Please,
* see {@link CorsConfiguration#setAllowedOrigins(List)} for details.
* <p>By default all origins are allowed unless {@code originPatterns} is
* also set in which case {@code originPatterns} is used instead.
*/
public CorsRegistration allowedOrigins(String... origins) {
this.config.setAllowedOrigins(new ArrayList<>(Arrays.asList(origins)));
return this;
}
/**
* Alternative to {@link #allowCredentials} that supports origins declared
* via wildcard patterns. Please, see
* @link CorsConfiguration#setAllowedOriginPatterns(List)} for details.
* <p>By default this is not set.
* @since 5.3
*/
public CorsRegistration allowedOriginPatterns(String... patterns) {
this.config.setAllowedOriginPatterns(Arrays.asList(patterns));
return this;
}
/**
* Set the HTTP methods to allow, e.g. {@code "GET"}, {@code "POST"}, etc.
* <p>The special value {@code "*"} allows all methods.

View File

@@ -187,6 +187,9 @@ public abstract class AbstractHandlerMapping extends ApplicationObjectSupport
CorsConfiguration config = (this.corsConfigurationSource != null ? this.corsConfigurationSource.getCorsConfiguration(exchange) : null);
CorsConfiguration handlerConfig = getCorsConfiguration(handler, exchange);
config = (config != null ? config.combine(handlerConfig) : handlerConfig);
if (config != null) {
config.validateAllowCredentials();
}
if (!this.corsProcessor.process(config, exchange) || CorsUtils.isPreFlightRequest(request)) {
return REQUEST_HANDLED_HANDLER;
}

View File

@@ -87,7 +87,7 @@ public abstract class AbstractHandlerMethodMapping<T> extends AbstractHandlerMap
private static final CorsConfiguration ALLOW_CORS_CONFIG = new CorsConfiguration();
static {
ALLOW_CORS_CONFIG.addAllowedOrigin("*");
ALLOW_CORS_CONFIG.addAllowedOriginPattern("*");
ALLOW_CORS_CONFIG.addAllowedMethod("*");
ALLOW_CORS_CONFIG.addAllowedHeader("*");
ALLOW_CORS_CONFIG.setAllowCredentials(true);
@@ -485,9 +485,10 @@ public abstract class AbstractHandlerMethodMapping<T> extends AbstractHandlerMap
validateMethodMapping(handlerMethod, mapping);
this.mappingLookup.put(mapping, handlerMethod);
CorsConfiguration corsConfig = initCorsConfiguration(handler, method, mapping);
if (corsConfig != null) {
this.corsLookup.put(handlerMethod, corsConfig);
CorsConfiguration config = initCorsConfiguration(handler, method, mapping);
if (config != null) {
config.validateAllowCredentials();
this.corsLookup.put(handlerMethod, config);
}
this.registry.put(mapping, new MappingRegistration<>(mapping, handlerMethod));

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2019 the original author or authors.
* Copyright 2002-2020 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.
@@ -17,6 +17,7 @@
package org.springframework.web.reactive.config;
import java.util.Arrays;
import java.util.Collections;
import java.util.Map;
import org.junit.jupiter.api.Test;
@@ -56,11 +57,20 @@ public class CorsRegistryTests {
assertThat(configs.size()).isEqualTo(1);
CorsConfiguration config = configs.get("/foo");
assertThat(config.getAllowedOrigins()).isEqualTo(Arrays.asList("https://domain2.com", "https://domain2.com"));
assertThat(config.getAllowedMethods()).isEqualTo(Arrays.asList("DELETE"));
assertThat(config.getAllowedMethods()).isEqualTo(Collections.singletonList("DELETE"));
assertThat(config.getAllowedHeaders()).isEqualTo(Arrays.asList("header1", "header2"));
assertThat(config.getExposedHeaders()).isEqualTo(Arrays.asList("header3", "header4"));
assertThat(config.getAllowCredentials()).isEqualTo(false);
assertThat(config.getMaxAge()).isEqualTo(Long.valueOf(3600));
}
@Test
public void allowCredentials() {
this.registry.addMapping("/foo").allowCredentials(true);
CorsConfiguration config = this.registry.getCorsConfigurations().get("/foo");
assertThat(config.getAllowedOrigins())
.as("Globally origins=\"*\" and allowCredentials=true should be possible")
.containsExactly("*");
}
}

View File

@@ -113,7 +113,7 @@ public class CorsUrlHandlerMappingTests {
@Test
public void actualRequestWithGlobalPatternCorsConfig() throws Exception {
CorsConfiguration mappedConfig = new CorsConfiguration();
mappedConfig.addAllowedOriginPattern(".*\\.domain2.com");
mappedConfig.addAllowedOriginPattern("https://*.domain2.com");
this.handlerMapping.setCorsConfigurations(Collections.singletonMap("/welcome.html", mappedConfig));
String origin = "https://example.domain2.com";
@@ -122,7 +122,8 @@ public class CorsUrlHandlerMappingTests {
assertThat(actual).isNotNull();
assertThat(actual).isSameAs(this.welcomeController);
assertThat(exchange.getResponse().getHeaders().getFirst(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN)).isEqualTo("https://example.domain2.com");
assertThat(exchange.getResponse().getHeaders().getFirst(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN))
.isEqualTo("https://example.domain2.com");
}
@Test
@@ -197,7 +198,7 @@ public class CorsUrlHandlerMappingTests {
@Override
public CorsConfiguration getCorsConfiguration(ServerWebExchange exchange) {
CorsConfiguration config = new CorsConfiguration();
config.addAllowedOrigin("*");
config.addAllowedOriginPattern("*");
config.setAllowCredentials(true);
return config;
}

View File

@@ -68,7 +68,7 @@ class CrossOriginAnnotationIntegrationTests extends AbstractRequestMappingIntegr
context.register(WebConfig.class);
Properties props = new Properties();
props.setProperty("myOrigin", "https://site1.com");
props.setProperty("myOriginPattern", ".*\\.com");
props.setProperty("myOriginPattern", "https://*.com");
context.getEnvironment().getPropertySources().addFirst(new PropertiesPropertySource("ps", props));
context.register(PropertySourcesPlaceholderConfigurer.class);
context.refresh();
@@ -358,7 +358,7 @@ class CrossOriginAnnotationIntegrationTests extends AbstractRequestMappingIntegr
return "placeholder";
}
@CrossOrigin(originPatterns = ".*\\.com")
@CrossOrigin(originPatterns = "https://*.com")
@GetMapping("/origin-pattern-value-attribute")
public String customOriginPatternDefinedViaValueAttribute() {
return "pattern-value-attribute";
@@ -388,7 +388,7 @@ class CrossOriginAnnotationIntegrationTests extends AbstractRequestMappingIntegr
return "bar";
}
@CrossOrigin(allowCredentials = "true")
@CrossOrigin(originPatterns = "*", allowCredentials = "true")
@GetMapping("/baz")
public String baz() {
return "baz";

View File

@@ -161,8 +161,7 @@ class GlobalCorsConfigIntegrationTests extends AbstractRequestMappingIntegration
ResponseEntity<String> entity = performOptions("/ambiguous", this.headers, String.class);
assertThat(entity.getStatusCode()).isEqualTo(HttpStatus.OK);
assertThat(entity.getHeaders().getAccessControlAllowOrigin()).isEqualTo("http://localhost:9000");
assertThat(entity.getHeaders().getAccessControlAllowMethods())
.containsExactly(HttpMethod.GET);
assertThat(entity.getHeaders().getAccessControlAllowMethods()).containsExactly(HttpMethod.GET);
assertThat(entity.getHeaders().getAccessControlAllowCredentials()).isEqualTo(true);
assertThat(entity.getHeaders().get(HttpHeaders.VARY))
.containsExactly(HttpHeaders.ORIGIN, HttpHeaders.ACCESS_CONTROL_REQUEST_METHOD,
@@ -177,12 +176,9 @@ class GlobalCorsConfigIntegrationTests extends AbstractRequestMappingIntegration
@Override
protected void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/cors-restricted")
.allowedOrigins("https://foo")
.allowedMethods("GET", "POST");
registry.addMapping("/cors-restricted").allowedOrigins("https://foo").allowedMethods("GET", "POST");
registry.addMapping("/cors");
registry.addMapping("/ambiguous")
.allowedMethods("GET", "POST");
registry.addMapping("/ambiguous").allowedMethods("GET", "POST");
}
}