Suppress PREFLIGHT_AMBIGUOUS_MATCH if matches have no CORS config

Closes gh-26490
This commit is contained in:
Rossen Stoyanchev
2021-02-03 10:43:08 +00:00
parent 0575e6637c
commit 996c86f448
5 changed files with 215 additions and 79 deletions

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2020 the original author or authors.
* Copyright 2002-2021 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.
@@ -28,12 +28,19 @@ import org.junit.jupiter.api.Test;
import reactor.core.publisher.Mono;
import reactor.test.StepVerifier;
import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.PathContainer;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.testfixture.http.server.reactive.MockServerHttpRequest;
import org.springframework.web.testfixture.http.server.reactive.MockServerHttpResponse;
import org.springframework.web.testfixture.server.MockServerWebExchange;
import org.springframework.web.util.pattern.PathPattern;
import org.springframework.web.util.pattern.PathPatternParser;
@@ -105,6 +112,39 @@ public class HandlerMethodMappingTests {
StepVerifier.create(result).expectError(IllegalStateException.class).verify();
}
@Test // gh-26490
public void ambiguousMatchOnPreFlightRequestWithoutCorsConfig() throws Exception {
this.mapping.registerMapping("/f?o", this.handler, this.method1);
this.mapping.registerMapping("/fo?", this.handler, this.method2);
MockServerWebExchange exchange = MockServerWebExchange.from(
MockServerHttpRequest.options("http://example.org/foo")
.header(HttpHeaders.ORIGIN, "https://domain.com")
.header(HttpHeaders.ACCESS_CONTROL_REQUEST_METHOD, "GET"));
this.mapping.getHandler(exchange).block();
assertThat(exchange.getResponse().getStatusCode()).isEqualTo(HttpStatus.FORBIDDEN);
}
@Test // gh-26490
public void ambiguousMatchOnPreFlightRequestWithCorsConfig() throws Exception {
this.mapping.registerMapping("/f?o", this.handler, this.method1);
this.mapping.registerMapping("/fo?", this.handler, this.handler.getClass().getMethod("corsHandlerMethod"));
MockServerWebExchange exchange = MockServerWebExchange.from(
MockServerHttpRequest.options("http://example.org/foo")
.header(HttpHeaders.ORIGIN, "https://domain.com")
.header(HttpHeaders.ACCESS_CONTROL_REQUEST_METHOD, "GET"));
this.mapping.getHandler(exchange).block();
MockServerHttpResponse response = exchange.getResponse();
assertThat(response.getStatusCode()).isNull();
assertThat(response.getHeaders().getAccessControlAllowOrigin()).isEqualTo("https://domain.com");
assertThat(response.getHeaders().getAccessControlAllowMethods()).containsExactly(HttpMethod.GET);
}
@Test
public void registerMapping() {
String key1 = "/foo";
@@ -171,6 +211,17 @@ public class HandlerMethodMappingTests {
Collections.emptySet() : Collections.singleton(mapping));
}
@Override
protected CorsConfiguration initCorsConfiguration(Object handler, Method method, String mapping) {
CrossOrigin crossOrigin = AnnotatedElementUtils.findMergedAnnotation(method, CrossOrigin.class);
if (crossOrigin != null) {
CorsConfiguration corsConfig = new CorsConfiguration();
corsConfig.setAllowedOrigins(Collections.singletonList("http://domain.com"));
return corsConfig;
}
return null;
}
@Override
protected String getMatchingMapping(String pattern, ServerWebExchange exchange) {
PathContainer lookupPath = exchange.getRequest().getPath().pathWithinApplication();
@@ -200,6 +251,11 @@ public class HandlerMethodMappingTests {
@SuppressWarnings("unused")
public void handlerMethod2() {
}
@RequestMapping
@CrossOrigin(originPatterns = "*")
public void corsHandlerMethod() {
}
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2019 the original author or authors.
* Copyright 2002-2021 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.
@@ -160,9 +160,9 @@ class GlobalCorsConfigIntegrationTests extends AbstractRequestMappingIntegration
this.headers.add(HttpHeaders.ACCESS_CONTROL_REQUEST_METHOD, "GET");
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().getAccessControlAllowCredentials()).isEqualTo(true);
assertThat(entity.getHeaders().getAccessControlAllowOrigin()).isEqualTo("*");
assertThat(entity.getHeaders().getAccessControlAllowMethods()).containsExactly(HttpMethod.GET, HttpMethod.POST);
assertThat(entity.getHeaders().getAccessControlAllowCredentials()).isEqualTo(false);
assertThat(entity.getHeaders().get(HttpHeaders.VARY))
.containsExactly(HttpHeaders.ORIGIN, HttpHeaders.ACCESS_CONTROL_REQUEST_METHOD,
HttpHeaders.ACCESS_CONTROL_REQUEST_HEADERS);