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

@@ -53,6 +53,55 @@ public class CorsConfiguration {
public CorsConfiguration() {
}
/**
* Copy constructor.
*/
public CorsConfiguration(CorsConfiguration other) {
this.allowedOrigins = other.allowedOrigins;
this.allowedMethods = other.allowedMethods;
this.allowedHeaders = other.allowedHeaders;
this.exposedHeaders = other.exposedHeaders;
this.allowCredentials = other.allowCredentials;
this.maxAge = other.maxAge;
}
/**
* Combine the specified {@link CorsConfiguration} with this one.
* Properties of this configuration are overridden only by non-null properties
* of the provided one.
* @return the combined {@link CorsConfiguration}
*/
public CorsConfiguration combine(CorsConfiguration other) {
if (other == null) {
return this;
}
CorsConfiguration config = new CorsConfiguration(this);
config.setAllowedOrigins(combine(this.getAllowedOrigins(), other.getAllowedOrigins()));
config.setAllowedMethods(combine(this.getAllowedMethods(), other.getAllowedMethods()));
config.setAllowedHeaders(combine(this.getAllowedHeaders(), other.getAllowedHeaders()));
config.setExposedHeaders(combine(this.getExposedHeaders(), other.getExposedHeaders()));
Boolean allowCredentials = other.getAllowCredentials();
if (allowCredentials != null) {
config.setAllowCredentials(allowCredentials);
}
Long maxAge = other.getMaxAge();
if (maxAge != null) {
config.setMaxAge(maxAge);
}
return config;
}
private List<String> combine(List<String> source, List<String> other) {
if (other == null) {
return source;
}
if (source == null || source.contains("*")) {
return other;
}
List<String> combined = new ArrayList<String>(source);
combined.addAll(other);
return combined;
}
/**
* Configure origins to allow, e.g. "http://domain1.com". The special value
@@ -142,6 +191,9 @@ public class CorsConfiguration {
* <p>By default this is not set.
*/
public void setExposedHeaders(List<String> exposedHeaders) {
if (exposedHeaders != null && exposedHeaders.contains("*")) {
throw new IllegalArgumentException("'*' is not a valid exposed header value");
}
this.exposedHeaders = exposedHeaders;
}
@@ -149,6 +201,9 @@ public class CorsConfiguration {
* Add a single response header to expose.
*/
public void addExposedHeader(String exposedHeader) {
if ("*".equals(exposedHeader)) {
throw new IllegalArgumentException("'*' is not a valid exposed header value");
}
if (this.exposedHeaders == null) {
this.exposedHeaders = new ArrayList<String>();
}

View File

@@ -20,8 +20,7 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.*;
import org.junit.Before;
import org.junit.Test;
@@ -40,6 +39,115 @@ public class CorsConfigurationTests {
public void setup() {
config = new CorsConfiguration();
}
@Test
public void setNullValues() {
config.setAllowedOrigins(null);
assertNull(config.getAllowedOrigins());
config.setAllowedHeaders(null);
assertNull(config.getAllowedHeaders());
config.setAllowedMethods(null);
assertNull(config.getAllowedMethods());
config.setExposedHeaders(null);
assertNull(config.getExposedHeaders());
config.setAllowCredentials(null);
assertNull(config.getAllowCredentials());
config.setMaxAge(null);
assertNull(config.getMaxAge());
}
@Test
public void setValues() {
config.addAllowedOrigin("*");
assertEquals(Arrays.asList("*"), config.getAllowedOrigins());
config.addAllowedHeader("*");
assertEquals(Arrays.asList("*"), config.getAllowedHeaders());
config.addAllowedMethod("*");
assertEquals(Arrays.asList("*"), config.getAllowedMethods());
config.addExposedHeader("header1");
config.addExposedHeader("header2");
assertEquals(Arrays.asList("header1", "header2"), config.getExposedHeaders());
config.setAllowCredentials(true);
assertTrue(config.getAllowCredentials());
config.setMaxAge(123L);
assertEquals(new Long(123), config.getMaxAge());
}
@Test(expected = IllegalArgumentException.class)
public void asteriskWildCardOnAddExposedHeader() {
config.addExposedHeader("*");
}
@Test(expected = IllegalArgumentException.class)
public void asteriskWildCardOnSetExposedHeaders() {
config.setExposedHeaders(Arrays.asList("*"));
}
@Test
public void combineWithNull() {
config.setAllowedOrigins(Arrays.asList("*"));
config.combine(null);
assertEquals(Arrays.asList("*"), config.getAllowedOrigins());
}
@Test
public void combineWithNullProperties() {
config.addAllowedOrigin("*");
config.addAllowedHeader("header1");
config.addExposedHeader("header3");
config.addAllowedMethod(HttpMethod.GET.name());
config.setMaxAge(123L);
config.setAllowCredentials(true);
CorsConfiguration other = new CorsConfiguration();
config = config.combine(other);
assertEquals(Arrays.asList("*"), config.getAllowedOrigins());
assertEquals(Arrays.asList("header1"), config.getAllowedHeaders());
assertEquals(Arrays.asList("header3"), config.getExposedHeaders());
assertEquals(Arrays.asList(HttpMethod.GET.name()), config.getAllowedMethods());
assertEquals(new Long(123), config.getMaxAge());
assertTrue(config.getAllowCredentials());
}
@Test
public void combineWithAsteriskWildCard() {
config.addAllowedOrigin("*");
config.addAllowedHeader("*");
config.addAllowedMethod("*");
CorsConfiguration other = new CorsConfiguration();
other.addAllowedOrigin("http://domain.com");
other.addAllowedHeader("header1");
other.addExposedHeader("header2");
other.addAllowedMethod(HttpMethod.PUT.name());
config = config.combine(other);
assertEquals(Arrays.asList("http://domain.com"), config.getAllowedOrigins());
assertEquals(Arrays.asList("header1"), config.getAllowedHeaders());
assertEquals(Arrays.asList("header2"), config.getExposedHeaders());
assertEquals(Arrays.asList(HttpMethod.PUT.name()), config.getAllowedMethods());
}
@Test
public void combine() {
config.addAllowedOrigin("http://domain1.com");
config.addAllowedHeader("header1");
config.addExposedHeader("header3");
config.addAllowedMethod(HttpMethod.GET.name());
config.setMaxAge(123L);
config.setAllowCredentials(true);
CorsConfiguration other = new CorsConfiguration();
other.addAllowedOrigin("http://domain2.com");
other.addAllowedHeader("header2");
other.addExposedHeader("header4");
other.addAllowedMethod(HttpMethod.PUT.name());
other.setMaxAge(456L);
other.setAllowCredentials(false);
config = config.combine(other);
assertEquals(Arrays.asList("http://domain1.com", "http://domain2.com"), config.getAllowedOrigins());
assertEquals(Arrays.asList("header1", "header2"), config.getAllowedHeaders());
assertEquals(Arrays.asList("header3", "header4"), config.getExposedHeaders());
assertEquals(Arrays.asList(HttpMethod.GET.name(), HttpMethod.PUT.name()), config.getAllowedMethods());
assertEquals(new Long(456), config.getMaxAge());
assertFalse(config.getAllowCredentials());
}
@Test
public void checkOriginAllowed() {

View File

@@ -0,0 +1,59 @@
/*
* 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.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.springframework.web.cors.CorsConfiguration;
/**
* Assist with the registration of {@link CorsConfiguration} mapped to one or more path patterns.
* @author Sebastien Deleuze
*
* @since 4.2
* @see CrossOriginRegistration
*/
public class CrossOriginConfigurer {
private final List<CrossOriginRegistration> registrations = new ArrayList<CrossOriginRegistration>();
/**
* Enable cross origin requests on the specified path patterns. If no path pattern is specified,
* cross-origin request handling is mapped on "/**" .
*
* <p>By default, all origins, all headers and credentials are allowed. Max age is set to 30 minutes.</p>
*/
public CrossOriginRegistration enableCrossOrigin(String... pathPatterns) {
CrossOriginRegistration registration = new CrossOriginRegistration(pathPatterns);
this.registrations.add(registration);
return registration;
}
protected Map<String, CorsConfiguration> getCorsConfigurations() {
Map<String, CorsConfiguration> configs = new HashMap<String, CorsConfiguration>();
for (CrossOriginRegistration registration : this.registrations) {
for (String pathPattern : registration.getPathPatterns()) {
configs.put(pathPattern, registration.getCorsConfiguration());
}
}
return configs;
}
}

View File

@@ -0,0 +1,92 @@
/*
* 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.ArrayList;
import java.util.Arrays;
import org.springframework.http.HttpMethod;
import org.springframework.web.cors.CorsConfiguration;
/**
* Assists with the creation of a {@link CorsConfiguration} mapped to one or more path patterns.
* If no path pattern is specified, cross-origin request handling is mapped on "/**" .
*
* <p>By default, all origins, all headers, credentials and GET, HEAD, POST methods are allowed.
* Max age is set to 30 minutes.</p>
*
* @author Sebastien Deleuze
* @since 4.2
*/
public class CrossOriginRegistration {
private final String[] pathPatterns;
private final CorsConfiguration config;
public CrossOriginRegistration(String... pathPatterns) {
this.pathPatterns = (pathPatterns.length == 0 ? new String[]{ "/**" } : pathPatterns);
// Same default values than @CrossOrigin annotation + allows simple methods
this.config = new CorsConfiguration();
this.config.addAllowedOrigin("*");
this.config.addAllowedMethod(HttpMethod.GET.name());
this.config.addAllowedMethod(HttpMethod.HEAD.name());
this.config.addAllowedMethod(HttpMethod.POST.name());
this.config.addAllowedHeader("*");
this.config.setAllowCredentials(true);
this.config.setMaxAge(1800L);
}
public CrossOriginRegistration allowedOrigins(String... origins) {
this.config.setAllowedOrigins(new ArrayList<String>(Arrays.asList(origins)));
return this;
}
public CrossOriginRegistration allowedMethods(String... methods) {
this.config.setAllowedMethods(new ArrayList<String>(Arrays.asList(methods)));
return this;
}
public CrossOriginRegistration allowedHeaders(String... headers) {
this.config.setAllowedHeaders(new ArrayList<String>(Arrays.asList(headers)));
return this;
}
public CrossOriginRegistration exposedHeaders(String... headers) {
this.config.setExposedHeaders(new ArrayList<String>(Arrays.asList(headers)));
return this;
}
public CrossOriginRegistration maxAge(long maxAge) {
this.config.setMaxAge(maxAge);
return this;
}
public CrossOriginRegistration allowCredentials(boolean allowCredentials) {
this.config.setAllowCredentials(allowCredentials);
return this;
}
protected String[] getPathPatterns() {
return this.pathPatterns;
}
protected CorsConfiguration getCorsConfiguration() {
return this.config;
}
}

View File

@@ -132,4 +132,9 @@ public class DelegatingWebMvcConfiguration extends WebMvcConfigurationSupport {
this.configurers.configureHandlerExceptionResolvers(exceptionResolvers);
}
@Override
protected void configureCrossOrigin(CrossOriginConfigurer configurer) {
this.configurers.configureCrossOrigin(configurer);
}
}

View File

@@ -66,6 +66,7 @@ import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.support.ConfigurableWebBindingInitializer;
import org.springframework.web.context.ServletContextAware;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.method.support.CompositeUriComponentsContributor;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.method.support.HandlerMethodReturnValueHandler;
@@ -199,6 +200,8 @@ public class WebMvcConfigurationSupport implements ApplicationContextAware, Serv
private ContentNegotiationManager contentNegotiationManager;
private List<HttpMessageConverter<?>> messageConverters;
private Map<String, CorsConfiguration> corsConfigurations;
/**
@@ -236,6 +239,7 @@ public class WebMvcConfigurationSupport implements ApplicationContextAware, Serv
handlerMapping.setOrder(0);
handlerMapping.setInterceptors(getInterceptors());
handlerMapping.setContentNegotiationManager(mvcContentNegotiationManager());
handlerMapping.setCorsConfigurations(getCorsConfigurations());
PathMatchConfigurer configurer = getPathMatchConfigurer();
if (configurer.isUseSuffixPatternMatch() != null) {
@@ -367,6 +371,7 @@ public class WebMvcConfigurationSupport implements ApplicationContextAware, Serv
handlerMapping.setPathMatcher(mvcPathMatcher());
handlerMapping.setUrlPathHelper(mvcUrlPathHelper());
handlerMapping.setInterceptors(getInterceptors());
handlerMapping.setCorsConfigurations(getCorsConfigurations());
return handlerMapping;
}
@@ -386,6 +391,7 @@ public class WebMvcConfigurationSupport implements ApplicationContextAware, Serv
BeanNameUrlHandlerMapping mapping = new BeanNameUrlHandlerMapping();
mapping.setOrder(2);
mapping.setInterceptors(getInterceptors());
mapping.setCorsConfigurations(getCorsConfigurations());
return mapping;
}
@@ -405,6 +411,7 @@ public class WebMvcConfigurationSupport implements ApplicationContextAware, Serv
handlerMapping.setUrlPathHelper(mvcUrlPathHelper());
handlerMapping.setInterceptors(new HandlerInterceptor[] {
new ResourceUrlProviderExposingInterceptor(mvcResourceUrlProvider())});
handlerMapping.setCorsConfigurations(getCorsConfigurations());
}
else {
handlerMapping = new EmptyHandlerMapping();
@@ -863,6 +870,26 @@ public class WebMvcConfigurationSupport implements ApplicationContextAware, Serv
protected void configureViewResolvers(ViewResolverRegistry registry) {
}
/**
* @since 4.2
*/
protected final Map<String, CorsConfiguration> getCorsConfigurations() {
if (this.corsConfigurations == null) {
CrossOriginConfigurer registry = new CrossOriginConfigurer();
configureCrossOrigin(registry);
this.corsConfigurations = registry.getCorsConfigurations();
}
return this.corsConfigurations;
}
/**
* Override this method to configure cross-origin requests handling.
* @since 4.2
* @see CrossOriginConfigurer
*/
protected void configureCrossOrigin(CrossOriginConfigurer configurer) {
}
private static final class EmptyHandlerMapping extends AbstractHandlerMapping {

View File

@@ -182,4 +182,10 @@ public interface WebMvcConfigurer {
*/
void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer);
/**
* Configure cross-origin requests handling.
* @since 4.2
*/
void configureCrossOrigin(CrossOriginConfigurer configurer);
}

View File

@@ -165,4 +165,12 @@ public abstract class WebMvcConfigurerAdapter implements WebMvcConfigurer {
public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
}
/**
* {@inheritDoc}
* <p>This implementation is empty.
*/
@Override
public void configureCrossOrigin(CrossOriginConfigurer configurer) {
}
}

View File

@@ -153,6 +153,13 @@ class WebMvcConfigurerComposite implements WebMvcConfigurer {
return selectSingleInstance(candidates, Validator.class);
}
@Override
public void configureCrossOrigin(CrossOriginConfigurer configurer) {
for (WebMvcConfigurer delegate : this.delegates) {
delegate.configureCrossOrigin(configurer);
}
}
private <T> T selectSingleInstance(List<T> instances, Class<T> instanceType) {
if (instances.size() > 1) {
throw new IllegalStateException(

View File

@@ -19,7 +19,9 @@ package org.springframework.web.servlet.handler;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@@ -80,6 +82,8 @@ public abstract class AbstractHandlerMapping extends WebApplicationObjectSupport
private final List<MappedInterceptor> mappedInterceptors = new ArrayList<MappedInterceptor>();
private CorsProcessor corsProcessor = new DefaultCorsProcessor();
private Map<String, CorsConfiguration> corsConfigurations = new HashMap<String, CorsConfiguration>();
/**
@@ -201,6 +205,28 @@ public abstract class AbstractHandlerMapping extends WebApplicationObjectSupport
Assert.notNull(corsProcessor, "CorsProcessor must not be null");
this.corsProcessor = corsProcessor;
}
/**
* Map the specified {@link CorsConfiguration} to the specified path.
*
* @param pathPattern the path to use. <p>Supports direct URL matches and Ant-style pattern matches.
* For syntax details, see the {@link org.springframework.util.AntPathMatcher} javadoc.
* @param config the CORS configuration to use
* @since 4.2
*/
public void registerCorsConfiguration(String pathPattern, CorsConfiguration config) {
this.corsConfigurations.put(pathPattern, config);
}
/**
* Set the {@link CorsConfiguration} map.
*
* @since 4.2
* @see #registerCorsConfiguration(String, CorsConfiguration)
*/
public void setCorsConfigurations(Map<String, CorsConfiguration> corsConfigurations) {
this.corsConfigurations = corsConfigurations;
}
/**
* Initializes the interceptors.
@@ -327,8 +353,7 @@ public abstract class AbstractHandlerMapping extends WebApplicationObjectSupport
}
HandlerExecutionChain executionChain = getHandlerExecutionChain(handler, request);
if (CorsUtils.isCorsRequest(request)) {
CorsConfiguration config = getCorsConfiguration(handler, request);
executionChain = getCorsHandlerExecutionChain(request, executionChain, config);
executionChain = getCorsHandlerExecutionChain(request, executionChain);
}
return executionChain;
}
@@ -415,7 +440,17 @@ public abstract class AbstractHandlerMapping extends WebApplicationObjectSupport
* HandlerInterceptor that makes CORS-related checks and adds CORS headers.
*/
protected HandlerExecutionChain getCorsHandlerExecutionChain(HttpServletRequest request,
HandlerExecutionChain chain, CorsConfiguration config) {
HandlerExecutionChain chain) {
CorsConfiguration globalConfig = null;
String lookupPath = this.urlPathHelper.getLookupPathForRequest(request);
for(Map.Entry<String, CorsConfiguration> entry : this.corsConfigurations.entrySet()) {
if(this.pathMatcher.match(entry.getKey(), lookupPath)) {
globalConfig = entry.getValue();
}
}
CorsConfiguration config = getCorsConfiguration(chain.getHandler(), request);
config = (globalConfig == null ? config : globalConfig.combine(config));
if (config != null) {
if (CorsUtils.isPreFlightRequest(request)) {

View File

@@ -436,17 +436,18 @@ public abstract class AbstractHandlerMethodMapping<T> extends AbstractHandlerMap
@Override
protected CorsConfiguration getCorsConfiguration(Object handler, HttpServletRequest request) {
CorsConfiguration corsConfig = super.getCorsConfiguration(handler, request);
if (handler instanceof HandlerMethod) {
HandlerMethod handlerMethod = (HandlerMethod) handler;
CorsConfiguration corsConfig = this.mappingRegistry.getCorsConfiguration(handlerMethod);
if (corsConfig != null) {
return corsConfig;
}
else if (handlerMethod.equals(PREFLIGHT_AMBIGUOUS_MATCH)) {
if (handlerMethod.equals(PREFLIGHT_AMBIGUOUS_MATCH)) {
return AbstractHandlerMethodMapping.ALLOW_CORS_CONFIG;
}
else {
CorsConfiguration corsConfigFromMethod = this.mappingRegistry.getCorsConfiguration(handlerMethod);
corsConfig = (corsConfig == null ? corsConfigFromMethod : corsConfig.combine(corsConfigFromMethod));
}
}
return null;
return corsConfig;
}

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) {