Add global CORS configuration capabilities

This commit adds JavaConfig based global CORS configuration
capabilities to Spring MVC. It is now possible to specify
multiple CORS configurations, each mapped on a path pattern,
by overriding
WebMvcConfigurerAdapter#configureCrossOrigin(CrossOriginConfigurer).

It is also possible to combine global and @CrossOrigin based
CORS configuration.

Issue: SPR-12933
This commit is contained in:
Sebastien Deleuze
2015-05-16 00:52:51 +02:00
parent 696a010e81
commit 49fff7513d
14 changed files with 548 additions and 13 deletions

View File

@@ -0,0 +1,84 @@
/*
* 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.servlet.config.annotation;
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 CrossOriginConfigurer}.
*
* @author Sebastien Deleuze
*/
public class CrossOriginConfigurerTests {
private CrossOriginConfigurer configurer;
@Before
public void setUp() {
this.configurer = new CrossOriginConfigurer();
}
@Test
public void noCrossOriginConfigured() {
assertTrue(this.configurer.getCorsConfigurations().isEmpty());
}
@Test
public void multipleCrossOriginConfigured() {
this.configurer.enableCrossOrigin("/foo");
this.configurer.enableCrossOrigin("/bar");
assertEquals(2, this.configurer.getCorsConfigurations().size());
}
@Test
public void defaultCrossOriginRegistration() {
this.configurer.enableCrossOrigin();
Map<String, CorsConfiguration> configs = this.configurer.getCorsConfigurations();
assertEquals(1, configs.size());
CorsConfiguration config = configs.get("/**");
assertEquals(Arrays.asList("*"), config.getAllowedOrigins());
assertEquals(Arrays.asList("GET", "HEAD", "POST"), config.getAllowedMethods());
assertEquals(Arrays.asList("*"), config.getAllowedHeaders());
assertEquals(true, config.getAllowCredentials());
assertEquals(Long.valueOf(1800), config.getMaxAge());
}
@Test
public void customizedCrossOriginRegistration() {
this.configurer.enableCrossOrigin("/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.configurer.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

@@ -18,6 +18,7 @@ package org.springframework.web.servlet.config.annotation;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.MapperFeature;
@@ -56,6 +57,7 @@ import org.springframework.web.context.request.async.CallableProcessingIntercept
import org.springframework.web.context.request.async.DeferredResultProcessingInterceptor;
import org.springframework.web.context.request.async.DeferredResultProcessingInterceptorAdapter;
import org.springframework.web.context.support.StaticWebApplicationContext;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.method.annotation.ModelAttributeMethodProcessor;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.method.support.HandlerMethodReturnValueHandler;
@@ -269,6 +271,13 @@ public class WebMvcConfigurationSupportExtensionTests {
assertEquals("/", accessor.getPropertyValue("prefix"));
assertEquals(".jsp", accessor.getPropertyValue("suffix"));
}
@Test
public void crossOrigin() {
Map<String, CorsConfiguration> configs = this.config.getCorsConfigurations();
assertEquals(1, configs.size());
assertEquals("*", configs.get("/resources/**").getAllowedOrigins().get(0));
}
@Controller
@@ -393,6 +402,12 @@ public class WebMvcConfigurationSupportExtensionTests {
public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
configurer.enable("default");
}
@Override
public void configureCrossOrigin(CrossOriginConfigurer registry) {
registry.enableCrossOrigin("/resources/**");
}
}
private class TestPathHelper extends UrlPathHelper {}

View File

@@ -63,7 +63,7 @@ public class CorsAbstractHandlerMappingTests {
@Test
public void actualRequestWithoutCorsConfigurationProvider() throws Exception {
this.request.setMethod(RequestMethod.GET.name());
this.request.setRequestURI("/notcors");
this.request.setRequestURI("/foo");
this.request.addHeader(HttpHeaders.ORIGIN, "http://domain2.com/test.html");
this.request.addHeader(HttpHeaders.ACCESS_CONTROL_REQUEST_METHOD, "GET");
HandlerExecutionChain chain = handlerMapping.getHandler(this.request);
@@ -73,7 +73,7 @@ public class CorsAbstractHandlerMappingTests {
@Test
public void preflightRequestWithoutCorsConfigurationProvider() throws Exception {
this.request.setMethod(RequestMethod.OPTIONS.name());
this.request.setRequestURI("/notcors");
this.request.setRequestURI("/foo");
this.request.addHeader(HttpHeaders.ORIGIN, "http://domain2.com/test.html");
this.request.addHeader(HttpHeaders.ACCESS_CONTROL_REQUEST_METHOD, "GET");
HandlerExecutionChain chain = handlerMapping.getHandler(this.request);
@@ -106,6 +106,39 @@ public class CorsAbstractHandlerMappingTests {
assertNotNull(config);
assertArrayEquals(config.getAllowedOrigins().toArray(), new String[]{"*"});
}
@Test
public void actualRequestWithMappedCorsConfiguration() throws Exception {
CorsConfiguration config = new CorsConfiguration();
config.addAllowedOrigin("*");
this.handlerMapping.registerCorsConfiguration("/foo", config);
this.request.setMethod(RequestMethod.GET.name());
this.request.setRequestURI("/foo");
this.request.addHeader(HttpHeaders.ORIGIN, "http://domain2.com/test.html");
this.request.addHeader(HttpHeaders.ACCESS_CONTROL_REQUEST_METHOD, "GET");
HandlerExecutionChain chain = handlerMapping.getHandler(this.request);
assertTrue(chain.getHandler() instanceof SimpleHandler);
config = getCorsConfiguration(chain, false);
assertNotNull(config);
assertArrayEquals(config.getAllowedOrigins().toArray(), new String[]{"*"});
}
@Test
public void preflightRequestWithMappedCorsConfiguration() throws Exception {
CorsConfiguration config = new CorsConfiguration();
config.addAllowedOrigin("*");
this.handlerMapping.registerCorsConfiguration("/foo", config);
this.request.setMethod(RequestMethod.OPTIONS.name());
this.request.setRequestURI("/foo");
this.request.addHeader(HttpHeaders.ORIGIN, "http://domain2.com/test.html");
this.request.addHeader(HttpHeaders.ACCESS_CONTROL_REQUEST_METHOD, "GET");
HandlerExecutionChain chain = handlerMapping.getHandler(this.request);
assertNotNull(chain.getHandler());
assertTrue(chain.getHandler().getClass().getSimpleName().equals("PreFlightHandler"));
config = getCorsConfiguration(chain, true);
assertNotNull(config);
assertArrayEquals(config.getAllowedOrigins().toArray(), new String[]{"*"});
}
private CorsConfiguration getCorsConfiguration(HandlerExecutionChain chain, boolean isPreFlightRequest) {