Add Reactive CORS support

This is a port of Spring MVC CORS support for Spring Web Reactive:
 - CORS classes keep the same name but are in the
   web.cors.reactive package
 - CorsConfiguration is reused because not tied to Servlet API
 - CORS HandlerMapping integration is done at
   AbstractHandlerMapping level
 - AbstractUrlHandlerMapping and AbstractHandlerMethodMapping
   have been slightly modified to call
   AbstractHandlerMapping#processCorsRequest()
 - Both global CORS configuration + @CrossOrigin support have
   been implemented

Issue: SPR-14545
This commit is contained in:
Sebastien Deleuze
2016-10-04 19:19:42 +02:00
committed by Rossen Stoyanchev
parent 0cc330e8fc
commit e31a2f778b
25 changed files with 2179 additions and 26 deletions

View File

@@ -0,0 +1,71 @@
/*
* Copyright 2002-2015 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
*
* http://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.web.reactive.config;
import java.util.Arrays;
import java.util.Map;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import org.junit.Before;
import org.junit.Test;
import org.springframework.web.cors.CorsConfiguration;
/**
* Test fixture with a {@link CorsRegistry}.
*
* @author Sebastien Deleuze
*/
public class CorsRegistryTests {
private CorsRegistry registry;
@Before
public void setUp() {
this.registry = new CorsRegistry();
}
@Test
public void noMapping() {
assertTrue(this.registry.getCorsConfigurations().isEmpty());
}
@Test
public void multipleMappings() {
this.registry.addMapping("/foo");
this.registry.addMapping("/bar");
assertEquals(2, this.registry.getCorsConfigurations().size());
}
@Test
public void customizedMapping() {
this.registry.addMapping("/foo").allowedOrigins("http://domain2.com", "http://domain2.com")
.allowedMethods("DELETE").allowCredentials(false).allowedHeaders("header1", "header2")
.exposedHeaders("header3", "header4").maxAge(3600);
Map<String, CorsConfiguration> configs = this.registry.getCorsConfigurations();
assertEquals(1, configs.size());
CorsConfiguration config = configs.get("/foo");
assertEquals(Arrays.asList("http://domain2.com", "http://domain2.com"), config.getAllowedOrigins());
assertEquals(Arrays.asList("DELETE"), config.getAllowedMethods());
assertEquals(Arrays.asList("header1", "header2"), config.getAllowedHeaders());
assertEquals(Arrays.asList("header3", "header4"), config.getExposedHeaders());
assertEquals(false, config.getAllowCredentials());
assertEquals(Long.valueOf(3600), config.getMaxAge());
}
}

View File

@@ -0,0 +1,182 @@
/*
* Copyright 2002-2016 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
*
* http://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.web.reactive.handler;
import java.net.URISyntaxException;
import java.util.Collections;
import static org.junit.Assert.*;
import static org.junit.Assert.assertSame;
import org.junit.Before;
import org.junit.Test;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.mock.http.server.reactive.test.MockServerHttpRequest;
import org.springframework.mock.http.server.reactive.test.MockServerHttpResponse;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.reactive.CorsConfigurationSource;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.adapter.DefaultServerWebExchange;
import org.springframework.web.server.session.MockWebSessionManager;
import org.springframework.web.server.session.WebSessionManager;
/**
* Unit tests for CORS support at {@link AbstractUrlHandlerMapping} level.
*
* @author Sebastien Deleuze
* @author Rossen Stoyanchev
*/
public class CorsAbstractUrlHandlerMappingTests {
private AnnotationConfigApplicationContext wac;
private TestUrlHandlerMapping handlerMapping;
private Object mainController;
private CorsAwareHandler corsConfigurationSourceController;
@Before
public void setup() {
wac = new AnnotationConfigApplicationContext();
wac.register(WebConfig.class);
wac.refresh();
handlerMapping = (TestUrlHandlerMapping) wac.getBean("handlerMapping");
mainController = wac.getBean("mainController");
corsConfigurationSourceController = (CorsAwareHandler) wac.getBean("corsConfigurationSourceController");
}
@Test
public void actualRequestWithoutCorsConfigurationProvider() throws Exception {
ServerWebExchange exchange = createExchange(HttpMethod.GET, "/welcome.html", "http://domain2.com", "GET");
Object actual = handlerMapping.getHandler(exchange).block();
assertNotNull(actual);
assertSame(mainController, actual);
}
@Test
public void preflightRequestWithoutCorsConfigurationProvider() throws Exception {
ServerWebExchange exchange = createExchange(HttpMethod.OPTIONS, "/welcome.html", "http://domain2.com", "GET");
Object actual = handlerMapping.getHandler(exchange).block();
assertNotNull(actual);
assertEquals("NoOpHandler", actual.getClass().getSimpleName());
assertNull(exchange.getResponse().getHeaders().getFirst(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN));
}
@Test
public void actualRequestWithCorsConfigurationProvider() throws Exception {
ServerWebExchange exchange = createExchange(HttpMethod.GET, "/cors.html", "http://domain2.com", "GET");
Object actual = handlerMapping.getHandler(exchange).block();
assertNotNull(actual);
assertSame(corsConfigurationSourceController, actual);
CorsConfiguration config = ((CorsConfigurationSource)actual).getCorsConfiguration(createExchange(HttpMethod.GET, "", "",""));
assertNotNull(config);
assertArrayEquals(config.getAllowedOrigins().toArray(), new String[]{"*"});
assertEquals("*", exchange.getResponse().getHeaders().getFirst(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN));
}
@Test
public void preflightRequestWithCorsConfigurationProvider() throws Exception {
ServerWebExchange exchange = createExchange(HttpMethod.OPTIONS, "/cors.html", "http://domain2.com", "GET");
Object actual = handlerMapping.getHandler(exchange).block();
assertNotNull(actual);
assertEquals("NoOpHandler", actual.getClass().getSimpleName());
assertEquals("*", exchange.getResponse().getHeaders().getFirst(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN));
}
@Test
public void actualRequestWithMappedCorsConfiguration() throws Exception {
CorsConfiguration mappedConfig = new CorsConfiguration();
mappedConfig.addAllowedOrigin("*");
this.handlerMapping.setCorsConfigurations(Collections.singletonMap("/welcome.html", mappedConfig));
ServerWebExchange exchange = createExchange(HttpMethod.GET, "/welcome.html", "http://domain2.com", "GET");
Object actual = handlerMapping.getHandler(exchange).block();
assertNotNull(actual);
assertSame(mainController, actual);
assertEquals("*", exchange.getResponse().getHeaders().getFirst(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN));
}
@Test
public void preflightRequestWithMappedCorsConfiguration() throws Exception {
CorsConfiguration mappedConfig = new CorsConfiguration();
mappedConfig.addAllowedOrigin("*");
this.handlerMapping.setCorsConfigurations(Collections.singletonMap("/welcome.html", mappedConfig));
ServerWebExchange exchange = createExchange(HttpMethod.OPTIONS, "/welcome.html", "http://domain2.com", "GET");
Object actual = handlerMapping.getHandler(exchange).block();
assertNotNull(actual);
assertEquals("NoOpHandler", actual.getClass().getSimpleName());
assertEquals("*", exchange.getResponse().getHeaders().getFirst(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN));
}
private ServerWebExchange createExchange(HttpMethod method, String path, String origin,
String accessControlRequestMethod) throws URISyntaxException {
ServerHttpRequest request = new MockServerHttpRequest(method, "http://localhost" + path);
request.getHeaders().add(HttpHeaders.ORIGIN, origin);
request.getHeaders().add(HttpHeaders.ACCESS_CONTROL_REQUEST_METHOD, accessControlRequestMethod);
WebSessionManager sessionManager = new MockWebSessionManager();
return new DefaultServerWebExchange(request, new MockServerHttpResponse(), sessionManager);
}
@Configuration
static class WebConfig {
@Bean @SuppressWarnings("unused")
public TestUrlHandlerMapping handlerMapping() {
TestUrlHandlerMapping hm = new TestUrlHandlerMapping();
hm.setUseTrailingSlashMatch(true);
hm.registerHandler("/welcome.html", mainController());
hm.registerHandler("/cors.html", corsConfigurationSourceController());
return hm;
}
@Bean
public Object mainController() {
return new Object();
}
@Bean
public CorsAwareHandler corsConfigurationSourceController() {
return new CorsAwareHandler();
}
}
static class TestUrlHandlerMapping extends AbstractUrlHandlerMapping {
}
static class CorsAwareHandler implements CorsConfigurationSource {
@Override
public CorsConfiguration getCorsConfiguration(ServerWebExchange exchange) {
CorsConfiguration config = new CorsConfiguration();
config.addAllowedOrigin("*");
return config;
}
}
}

View File

@@ -27,6 +27,7 @@ import org.springframework.http.server.reactive.HttpHandler;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.reactive.DispatcherHandler;
import org.springframework.web.server.adapter.WebHttpHandlerBuilder;
import org.springframework.web.server.handler.ResponseStatusExceptionHandler;
import static org.springframework.http.RequestEntity.get;
@@ -46,6 +47,7 @@ public abstract class AbstractRequestMappingIntegrationTests extends AbstractHtt
this.applicationContext = initApplicationContext();
return WebHttpHandlerBuilder
.webHandler(new DispatcherHandler(this.applicationContext))
.exceptionHandlers(new ResponseStatusExceptionHandler())
.build();
}

View File

@@ -0,0 +1,183 @@
/*
* Copyright 2002-2016 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
*
* http://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.web.reactive.result.method.annotation;
import static org.junit.Assert.*;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.HttpClientErrorException;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.reactive.config.CorsRegistry;
import org.springframework.web.reactive.config.WebReactiveConfiguration;
/**
* @author Sebastien Deleuze
*/
public class CorsConfigurationIntegrationTests extends AbstractRequestMappingIntegrationTests {
// JDK default HTTP client blacklist headers like Origin
private RestTemplate restTemplate = new RestTemplate(new HttpComponentsClientHttpRequestFactory());
@Override
protected ApplicationContext initApplicationContext() {
AnnotationConfigApplicationContext wac = new AnnotationConfigApplicationContext();
wac.register(WebConfig.class);
wac.refresh();
return wac;
}
@Override
RestTemplate getRestTemplate() {
return this.restTemplate;
}
@Test
public void actualRequestWithCorsEnabled() throws Exception {
HttpHeaders headers = new HttpHeaders();
headers.add(HttpHeaders.ORIGIN, "http://localhost:9000");
HttpEntity<?> requestEntity = new HttpEntity(headers);
ResponseEntity<String> entity = this.restTemplate.exchange(getUrl("/cors"),
HttpMethod.GET, requestEntity, String.class);
assertEquals(HttpStatus.OK, entity.getStatusCode());
assertEquals("http://localhost:9000", entity.getHeaders().getAccessControlAllowOrigin());
assertEquals("cors", entity.getBody());
}
@Test
public void actualRequestWithCorsRejected() throws Exception {
HttpHeaders headers = new HttpHeaders();
headers.add(HttpHeaders.ORIGIN, "http://localhost:9000");
HttpEntity<?> requestEntity = new HttpEntity(headers);
try {
this.restTemplate.exchange(getUrl("/cors-restricted"), HttpMethod.GET,
requestEntity, String.class);
}
catch (HttpClientErrorException e) {
assertEquals(HttpStatus.FORBIDDEN, e.getStatusCode());
return;
}
fail();
}
@Test
public void actualRequestWithoutCorsEnabled() throws Exception {
HttpHeaders headers = new HttpHeaders();
headers.add(HttpHeaders.ORIGIN, "http://localhost:9000");
HttpEntity<?> requestEntity = new HttpEntity(headers);
ResponseEntity<String> entity = this.restTemplate.exchange(getUrl("/welcome"),
HttpMethod.GET, requestEntity, String.class);
assertEquals(HttpStatus.OK, entity.getStatusCode());
assertNull(entity.getHeaders().getAccessControlAllowOrigin());
assertEquals("welcome", entity.getBody());
}
@Test
public void preflightRequestWithCorsEnabled() throws Exception {
HttpHeaders headers = new HttpHeaders();
headers.add(HttpHeaders.ORIGIN, "http://localhost:9000");
headers.add(HttpHeaders.ACCESS_CONTROL_REQUEST_METHOD, "GET");
HttpEntity<?> requestEntity = new HttpEntity(headers);
ResponseEntity<String> entity = this.restTemplate.exchange(getUrl("/cors"),
HttpMethod.OPTIONS, requestEntity, String.class);
assertEquals(HttpStatus.OK, entity.getStatusCode());
assertEquals("http://localhost:9000", entity.getHeaders().getAccessControlAllowOrigin());
}
@Test
public void preflightRequestWithCorsRejected() throws Exception {
HttpHeaders headers = new HttpHeaders();
headers.add(HttpHeaders.ORIGIN, "http://localhost:9000");
headers.add(HttpHeaders.ACCESS_CONTROL_REQUEST_METHOD, "GET");
HttpEntity<?> requestEntity = new HttpEntity(headers);
try {
this.restTemplate.exchange(getUrl("/cors-restricted"), HttpMethod.OPTIONS,
requestEntity, String.class);
}
catch (HttpClientErrorException e) {
assertEquals(HttpStatus.FORBIDDEN, e.getStatusCode());
return;
}
fail();
}
@Test
public void preflightRequestWithoutCorsEnabled() throws Exception {
HttpHeaders headers = new HttpHeaders();
headers.add(HttpHeaders.ORIGIN, "http://localhost:9000");
headers.add(HttpHeaders.ACCESS_CONTROL_REQUEST_METHOD, "GET");
HttpEntity<?> requestEntity = new HttpEntity(headers);
try {
this.restTemplate.exchange(getUrl("/welcome"), HttpMethod.OPTIONS,
requestEntity, String.class);
}
catch (HttpClientErrorException e) {
assertEquals(HttpStatus.FORBIDDEN, e.getStatusCode());
return;
}
fail();
}
private String getUrl(String path) {
return "http://localhost:" + this.port + path;
}
@Configuration
@ComponentScan(resourcePattern = "**/CorsConfigurationIntegrationTests*.class")
@SuppressWarnings({"unused", "WeakerAccess"})
static class WebConfig extends WebReactiveConfiguration {
@Override
protected void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/cors-restricted").allowedOrigins("http://foo");
registry.addMapping("/cors");
}
}
@RestController
static class TestController {
@GetMapping("/welcome")
public String welcome() {
return "welcome";
}
@GetMapping("/cors")
public String cors() {
return "cors";
}
@GetMapping("/cors-restricted")
public String corsRestricted() {
return "corsRestricted";
}
}
}

View File

@@ -0,0 +1,345 @@
/*
* Copyright 2002-2016 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
*
* http://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.web.reactive.result.method.annotation;
import java.util.Properties;
import static org.junit.Assert.*;
import static org.junit.Assert.assertArrayEquals;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.support.PropertySourcesPlaceholderConfigurer;
import org.springframework.core.env.PropertiesPropertySource;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.reactive.config.WebReactiveConfiguration;
/**
* @author Sebastien Deleuze
*/
public class CrossOriginAnnotationIntegrationTests extends AbstractRequestMappingIntegrationTests {
// JDK default HTTP client blacklist headers like Origin
private RestTemplate restTemplate = new RestTemplate(new HttpComponentsClientHttpRequestFactory());
@Override
protected ApplicationContext initApplicationContext() {
AnnotationConfigApplicationContext wac = new AnnotationConfigApplicationContext();
wac.register(WebConfig.class);
Properties props = new Properties();
props.setProperty("myOrigin", "http://site1.com");
wac.getEnvironment().getPropertySources().addFirst(new PropertiesPropertySource("ps", props));
wac.register(PropertySourcesPlaceholderConfigurer.class);
wac.refresh();
return wac;
}
@Override
RestTemplate getRestTemplate() {
return this.restTemplate;
}
@Test
public void actualGetRequestWithoutAnnotation() {
HttpHeaders headers = new HttpHeaders();
headers.add(HttpHeaders.ORIGIN, "http://site1.com");
HttpEntity<?> requestEntity = new HttpEntity(headers);
ResponseEntity<String> entity = this.restTemplate.exchange(getUrl("/no"),
HttpMethod.GET, requestEntity, String.class);
assertEquals(HttpStatus.OK, entity.getStatusCode());
assertNull(entity.getHeaders().getAccessControlAllowOrigin());
assertEquals("no", entity.getBody());
}
@Test
public void actualPostRequestWithoutAnnotation() {
HttpHeaders headers = new HttpHeaders();
headers.add(HttpHeaders.ORIGIN, "http://site1.com");
HttpEntity<?> requestEntity = new HttpEntity(headers);
ResponseEntity<String> entity = this.restTemplate.exchange(getUrl("/no"),
HttpMethod.POST, requestEntity, String.class);
assertEquals(HttpStatus.OK, entity.getStatusCode());
assertNull(entity.getHeaders().getAccessControlAllowOrigin());
assertEquals("no-post", entity.getBody());
}
@Test
public void actualRequestWithDefaultAnnotation() {
HttpHeaders headers = new HttpHeaders();
headers.add(HttpHeaders.ORIGIN, "http://site1.com");
HttpEntity<?> requestEntity = new HttpEntity(headers);
ResponseEntity<String> entity = this.restTemplate.exchange(getUrl("/default"),
HttpMethod.GET, requestEntity, String.class);
assertEquals(HttpStatus.OK, entity.getStatusCode());
assertEquals("http://site1.com", entity.getHeaders().getAccessControlAllowOrigin());
assertEquals(true, entity.getHeaders().getAccessControlAllowCredentials());
assertEquals("default", entity.getBody());
}
@Test
public void preflightRequestWithDefaultAnnotation() {
HttpHeaders headers = new HttpHeaders();
headers.add(HttpHeaders.ORIGIN, "http://site1.com");
headers.add(HttpHeaders.ACCESS_CONTROL_REQUEST_METHOD, "GET");
HttpEntity<?> requestEntity = new HttpEntity(headers);
ResponseEntity<Void> entity = this.restTemplate.exchange(getUrl("/default"),
HttpMethod.OPTIONS, requestEntity, Void.class);
assertEquals(HttpStatus.OK, entity.getStatusCode());
assertEquals("http://site1.com", entity.getHeaders().getAccessControlAllowOrigin());
assertEquals(1800, entity.getHeaders().getAccessControlMaxAge());
assertEquals(true, entity.getHeaders().getAccessControlAllowCredentials());
}
@Test
public void actualRequestWithDefaultAnnotationAndNoOrigin() {
HttpHeaders headers = new HttpHeaders();
HttpEntity<?> requestEntity = new HttpEntity(headers);
ResponseEntity<String> entity = this.restTemplate.exchange(getUrl("/default"),
HttpMethod.GET, requestEntity, String.class);
assertEquals(HttpStatus.OK, entity.getStatusCode());
assertNull(entity.getHeaders().getAccessControlAllowOrigin());
assertEquals("default", entity.getBody());
}
@Test
public void actualRequestWithCustomizedAnnotation() {
HttpHeaders headers = new HttpHeaders();
headers.add(HttpHeaders.ORIGIN, "http://site1.com");
HttpEntity<?> requestEntity = new HttpEntity(headers);
ResponseEntity<String> entity = this.restTemplate.exchange(getUrl("/customized"),
HttpMethod.GET, requestEntity, String.class);
assertEquals(HttpStatus.OK, entity.getStatusCode());
assertEquals("http://site1.com", entity.getHeaders().getAccessControlAllowOrigin());
assertEquals(false, entity.getHeaders().getAccessControlAllowCredentials());
assertEquals(-1, entity.getHeaders().getAccessControlMaxAge());
assertEquals("customized", entity.getBody());
}
@Test
public void preflightRequestWithCustomizedAnnotation() {
HttpHeaders headers = new HttpHeaders();
headers.add(HttpHeaders.ORIGIN, "http://site1.com");
headers.add(HttpHeaders.ACCESS_CONTROL_REQUEST_METHOD, "GET");
headers.add(HttpHeaders.ACCESS_CONTROL_REQUEST_HEADERS, "header1, header2");
HttpEntity<?> requestEntity = new HttpEntity(headers);
ResponseEntity<String> entity = this.restTemplate.exchange(getUrl("/customized"),
HttpMethod.OPTIONS, requestEntity, String.class);
assertEquals(HttpStatus.OK, entity.getStatusCode());
assertEquals("http://site1.com", entity.getHeaders().getAccessControlAllowOrigin());
assertArrayEquals(new HttpMethod[] {HttpMethod.GET}, entity.getHeaders().getAccessControlAllowMethods().toArray());
assertEquals(false, entity.getHeaders().getAccessControlAllowCredentials());
assertArrayEquals(new String[] {"header1", "header2"}, entity.getHeaders().getAccessControlAllowHeaders().toArray());
assertArrayEquals(new String[] {"header3", "header4"}, entity.getHeaders().getAccessControlExposeHeaders().toArray());
assertEquals(123, entity.getHeaders().getAccessControlMaxAge());
}
@Test
public void customOriginDefinedViaValueAttribute() {
HttpHeaders headers = new HttpHeaders();
headers.add(HttpHeaders.ORIGIN, "http://site1.com");
HttpEntity<?> requestEntity = new HttpEntity(headers);
ResponseEntity<String> entity = this.restTemplate.exchange(getUrl("/origin-value-attribute"),
HttpMethod.GET, requestEntity, String.class);
assertEquals(HttpStatus.OK, entity.getStatusCode());
assertEquals("http://site1.com", entity.getHeaders().getAccessControlAllowOrigin());
assertEquals("value-attribute", entity.getBody());
}
@Test
public void customOriginDefinedViaPlaceholder() {
HttpHeaders headers = new HttpHeaders();
headers.add(HttpHeaders.ORIGIN, "http://site1.com");
HttpEntity<?> requestEntity = new HttpEntity(headers);
ResponseEntity<String> entity = this.restTemplate.exchange(getUrl("/origin-placeholder"),
HttpMethod.GET, requestEntity, String.class);
assertEquals(HttpStatus.OK, entity.getStatusCode());
assertEquals("http://site1.com", entity.getHeaders().getAccessControlAllowOrigin());
assertEquals("placeholder", entity.getBody());
}
@Test
public void classLevel() {
HttpHeaders headers = new HttpHeaders();
headers.add(HttpHeaders.ORIGIN, "http://site1.com");
HttpEntity<?> requestEntity = new HttpEntity(headers);
ResponseEntity<String> entity = this.restTemplate.exchange(getUrl("/foo"),
HttpMethod.GET, requestEntity, String.class);
assertEquals(HttpStatus.OK, entity.getStatusCode());
assertEquals("*", entity.getHeaders().getAccessControlAllowOrigin());
assertEquals(false, entity.getHeaders().getAccessControlAllowCredentials());
assertEquals("foo", entity.getBody());
entity = this.restTemplate.exchange(getUrl("/bar"), HttpMethod.GET, requestEntity, String.class);
assertEquals(HttpStatus.OK, entity.getStatusCode());
assertEquals("*", entity.getHeaders().getAccessControlAllowOrigin());
assertEquals(false, entity.getHeaders().getAccessControlAllowCredentials());
assertEquals("bar", entity.getBody());
entity = this.restTemplate.exchange(getUrl("/baz"), HttpMethod.GET, requestEntity, String.class);
assertEquals(HttpStatus.OK, entity.getStatusCode());
assertEquals("http://site1.com", entity.getHeaders().getAccessControlAllowOrigin());
assertEquals(true, entity.getHeaders().getAccessControlAllowCredentials());
assertEquals("baz", entity.getBody());
}
@Test
public void ambiguousHeaderPreflightRequest() throws Exception {
HttpHeaders headers = new HttpHeaders();
headers.add(HttpHeaders.ORIGIN, "http://site1.com");
headers.add(HttpHeaders.ACCESS_CONTROL_REQUEST_METHOD, "GET");
headers.add(HttpHeaders.ACCESS_CONTROL_REQUEST_HEADERS, "header1");
HttpEntity<?> requestEntity = new HttpEntity(headers);
ResponseEntity<String> entity = this.restTemplate.exchange(getUrl("/ambiguous-header"),
HttpMethod.OPTIONS, requestEntity, String.class);
assertEquals(HttpStatus.OK, entity.getStatusCode());
assertEquals("http://site1.com", entity.getHeaders().getAccessControlAllowOrigin());
assertArrayEquals(new HttpMethod[] {HttpMethod.GET}, entity.getHeaders().getAccessControlAllowMethods().toArray());
assertEquals(true, entity.getHeaders().getAccessControlAllowCredentials());
assertArrayEquals(new String[] {"header1"}, entity.getHeaders().getAccessControlAllowHeaders().toArray());
}
@Test
public void ambiguousProducesPreflightRequest() throws Exception {
HttpHeaders headers = new HttpHeaders();
headers.add(HttpHeaders.ORIGIN, "http://site1.com");
headers.add(HttpHeaders.ACCESS_CONTROL_REQUEST_METHOD, "GET");
HttpEntity<?> requestEntity = new HttpEntity(headers);
ResponseEntity<String> entity = this.restTemplate.exchange(getUrl("/ambiguous-produces"),
HttpMethod.OPTIONS, requestEntity, String.class);
assertEquals(HttpStatus.OK, entity.getStatusCode());
assertEquals("http://site1.com", entity.getHeaders().getAccessControlAllowOrigin());
assertArrayEquals(new HttpMethod[] {HttpMethod.GET}, entity.getHeaders().getAccessControlAllowMethods().toArray());
assertEquals(true, entity.getHeaders().getAccessControlAllowCredentials());
}
private String getUrl(String path) {
return "http://localhost:" + this.port + path;
}
@Configuration
@ComponentScan(resourcePattern = "**/CrossOriginAnnotationIntegrationTests*")
@SuppressWarnings({"unused", "WeakerAccess"})
static class WebConfig extends WebReactiveConfiguration {
}
@RestController
private static class MethodLevelController {
@RequestMapping(path = "/no", method = RequestMethod.GET)
public String noAnnotation() {
return "no";
}
@RequestMapping(path = "/no", method = RequestMethod.POST)
public String noAnnotationPost() {
return "no-post";
}
@CrossOrigin
@RequestMapping(path = "/default", method = RequestMethod.GET)
public String defaultAnnotation() {
return "default";
}
@CrossOrigin
@RequestMapping(path = "/default", method = RequestMethod.GET, params = "q")
public void defaultAnnotationWithParams() {
}
@CrossOrigin
@RequestMapping(path = "/ambiguous-header", method = RequestMethod.GET, headers = "header1=a")
public void ambigousHeader1a() {
}
@CrossOrigin
@RequestMapping(path = "/ambiguous-header", method = RequestMethod.GET, headers = "header1=b")
public void ambigousHeader1b() {
}
@CrossOrigin
@RequestMapping(path = "/ambiguous-produces", method = RequestMethod.GET, produces = "application/xml")
public String ambigousProducesXml() {
return "<a></a>";
}
@CrossOrigin
@RequestMapping(path = "/ambiguous-produces", method = RequestMethod.GET, produces = "application/json")
public String ambigousProducesJson() {
return "{}";
}
@CrossOrigin(origins = { "http://site1.com", "http://site2.com" }, allowedHeaders = { "header1", "header2" },
exposedHeaders = { "header3", "header4" }, methods = RequestMethod.GET, maxAge = 123, allowCredentials = "false")
@RequestMapping(path = "/customized", method = { RequestMethod.GET, RequestMethod.POST })
public String customized() {
return "customized";
}
@CrossOrigin("http://site1.com")
@RequestMapping("/origin-value-attribute")
public String customOriginDefinedViaValueAttribute() {
return "value-attribute";
}
@CrossOrigin("${myOrigin}")
@RequestMapping("/origin-placeholder")
public String customOriginDefinedViaPlaceholder() {
return "placeholder";
}
}
@RestController
@CrossOrigin(allowCredentials = "false")
private static class ClassLevelController {
@RequestMapping(path = "/foo", method = RequestMethod.GET)
public String foo() {
return "foo";
}
@CrossOrigin
@RequestMapping(path = "/bar", method = RequestMethod.GET)
public String bar() {
return "bar";
}
@CrossOrigin(allowCredentials = "true")
@RequestMapping(path = "/baz", method = RequestMethod.GET)
public String baz() {
return "baz";
}
}
}