diff --git a/spring-web/src/main/java/org/springframework/web/cors/CorsConfigurationMapping.java b/spring-web/src/main/java/org/springframework/web/cors/CorsConfigurationMapping.java
new file mode 100644
index 0000000000..8fd146d643
--- /dev/null
+++ b/spring-web/src/main/java/org/springframework/web/cors/CorsConfigurationMapping.java
@@ -0,0 +1,134 @@
+/*
+ * 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.cors;
+
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import javax.servlet.http.HttpServletRequest;
+
+import org.springframework.util.AntPathMatcher;
+import org.springframework.util.Assert;
+import org.springframework.util.PathMatcher;
+import org.springframework.web.util.UrlPathHelper;
+
+/**
+ * Provide a per request {@link CorsConfiguration} instance based on a collection of
+ * {@link CorsConfiguration} mapped on path patterns.
+ *
+ *
Exact path mapping URIs (such as {@code "/admin"}) are supported as
+ * well as Ant-style path patterns (such as {@code "/admin/**"}).
+ *
+ * @author Sebastien Deleuze
+ * @since 4.2
+ */
+public class CorsConfigurationMapping implements CorsConfigurationSource {
+
+ private final Map corsConfigurations =
+ new LinkedHashMap();
+
+ private PathMatcher pathMatcher = new AntPathMatcher();
+
+ private UrlPathHelper urlPathHelper = new UrlPathHelper();
+
+
+ /**
+ * Set the PathMatcher implementation to use for matching URL paths
+ * against registered URL patterns. Default is AntPathMatcher.
+ * @see org.springframework.util.AntPathMatcher
+ */
+ public void setPathMatcher(PathMatcher pathMatcher) {
+ Assert.notNull(pathMatcher, "PathMatcher must not be null");
+ this.pathMatcher = pathMatcher;
+ }
+
+ /**
+ * Set if URL lookup should always use the full path within the current servlet
+ * context. Else, the path within the current servlet mapping is used if applicable
+ * (that is, in the case of a ".../*" servlet mapping in web.xml).
+ * Default is "false".
+ * @see org.springframework.web.util.UrlPathHelper#setAlwaysUseFullPath
+ */
+ public void setAlwaysUseFullPath(boolean alwaysUseFullPath) {
+ this.urlPathHelper.setAlwaysUseFullPath(alwaysUseFullPath);
+ }
+
+ /**
+ * Set if context path and request URI should be URL-decoded. Both are returned
+ * undecoded by the Servlet API, in contrast to the servlet path.
+ *
Uses either the request encoding or the default encoding according
+ * to the Servlet spec (ISO-8859-1).
+ * @see org.springframework.web.util.UrlPathHelper#setUrlDecode
+ */
+ public void setUrlDecode(boolean urlDecode) {
+ this.urlPathHelper.setUrlDecode(urlDecode);
+ }
+
+ /**
+ * Set if ";" (semicolon) content should be stripped from the request URI.
+ *
The default value is {@code true}.
+ * @see org.springframework.web.util.UrlPathHelper#setRemoveSemicolonContent(boolean)
+ */
+ public void setRemoveSemicolonContent(boolean removeSemicolonContent) {
+ this.urlPathHelper.setRemoveSemicolonContent(removeSemicolonContent);
+ }
+
+ /**
+ * Set the UrlPathHelper to use for resolution of lookup paths.
+ *
Use this to override the default UrlPathHelper with a custom subclass.
+ */
+ public void setUrlPathHelper(UrlPathHelper urlPathHelper) {
+ Assert.notNull(urlPathHelper, "UrlPathHelper must not be null");
+ this.urlPathHelper = urlPathHelper;
+ }
+
+ /**
+ * Set CORS configuration based on URL patterns.
+ */
+ public void setCorsConfigurations(Map corsConfigurations) {
+ this.corsConfigurations.clear();
+ if (corsConfigurations != null) {
+ this.corsConfigurations.putAll(corsConfigurations);
+ }
+ }
+
+ /**
+ * Get the CORS configuration.
+ */
+ public Map getCorsConfigurations() {
+ return Collections.unmodifiableMap(this.corsConfigurations);
+ }
+
+ /**
+ * Register a {@link CorsConfiguration} for the specified path pattern.
+ */
+ public void registerCorsConfiguration(String path, CorsConfiguration config) {
+ this.corsConfigurations.put(path, config);
+ }
+
+ @Override
+ public CorsConfiguration getCorsConfiguration(HttpServletRequest request) {
+ String lookupPath = this.urlPathHelper.getLookupPathForRequest(request);
+ for(Map.Entry entry : this.corsConfigurations.entrySet()) {
+ if (this.pathMatcher.match(entry.getKey(), lookupPath)) {
+ return entry.getValue();
+ }
+ }
+ return null;
+ }
+
+}
diff --git a/spring-web/src/main/java/org/springframework/web/filter/CorsFilter.java b/spring-web/src/main/java/org/springframework/web/filter/CorsFilter.java
new file mode 100644
index 0000000000..613f15e9ac
--- /dev/null
+++ b/spring-web/src/main/java/org/springframework/web/filter/CorsFilter.java
@@ -0,0 +1,89 @@
+/*
+ * 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.filter;
+
+import java.io.IOException;
+import javax.servlet.FilterChain;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.springframework.util.Assert;
+import org.springframework.web.cors.CorsConfiguration;
+import org.springframework.web.cors.CorsConfigurationMapping;
+import org.springframework.web.cors.CorsConfigurationSource;
+import org.springframework.web.cors.CorsProcessor;
+import org.springframework.web.cors.CorsUtils;
+import org.springframework.web.cors.DefaultCorsProcessor;
+
+/**
+ * {@link javax.servlet.Filter} that handles CORS preflight requests and intercepts CORS
+ * simple and actual requests thanks to a {@link CorsProcessor} implementation
+ * ({@link DefaultCorsProcessor} by default) in order to add the relevant CORS response
+ * headers (like {@code Access-Control-Allow-Origin}) using the provided
+ * {@link CorsConfigurationSource} (for example a {@link CorsConfigurationMapping} instance.
+ *
+ * This filter could be used in conjunction with {@link DelegatingFilterProxy} in order
+ * to help with its initialization.
+ *
+ * @author Sebastien Deleuze
+ * @since 4.2
+ * @see CORS W3C recommendation
+ */
+public class CorsFilter extends OncePerRequestFilter {
+
+ private CorsProcessor processor = new DefaultCorsProcessor();
+
+ private final CorsConfigurationSource source;
+
+
+ /**
+ * Constructor accepting a {@link CorsConfigurationSource}, this source will be used
+ * by the filter to find the {@link CorsConfiguration} to use for each incoming request.
+ * @see CorsConfigurationMapping
+ */
+ public CorsFilter(CorsConfigurationSource source) {
+ this.source = source;
+ }
+
+ /**
+ * Configure a custom {@link CorsProcessor} to use to apply the matched
+ * {@link CorsConfiguration} for a request.
+ *
By default {@link DefaultCorsProcessor} is used.
+ */
+ public void setCorsProcessor(CorsProcessor processor) {
+ Assert.notNull(processor, "CorsProcessor must not be null");
+ this.processor = processor;
+ }
+
+ @Override
+ protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response,
+ FilterChain filterChain) throws ServletException, IOException {
+
+ if (CorsUtils.isCorsRequest(request)) {
+ CorsConfiguration corsConfiguration = this.source.getCorsConfiguration(request);
+ if (corsConfiguration != null) {
+ boolean isValid = this.processor.processRequest(corsConfiguration, request, response);
+ if (!isValid || CorsUtils.isPreFlightRequest(request)) {
+ return;
+ }
+ }
+ }
+ filterChain.doFilter(request, response);
+ }
+
+}
diff --git a/spring-web/src/test/java/org/springframework/web/cors/CorsConfigurationMappingTests.java b/spring-web/src/test/java/org/springframework/web/cors/CorsConfigurationMappingTests.java
new file mode 100644
index 0000000000..ff0fd3b3a1
--- /dev/null
+++ b/spring-web/src/test/java/org/springframework/web/cors/CorsConfigurationMappingTests.java
@@ -0,0 +1,51 @@
+/*
+ * 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.cors;
+
+import static org.junit.Assert.*;
+import org.junit.Test;
+
+import org.springframework.http.HttpMethod;
+import org.springframework.mock.web.test.MockHttpServletRequest;
+
+/**
+ * Unit tests for {@link CorsConfigurationMapping}.
+ * @author Sebastien Deleuze
+ */
+public class CorsConfigurationMappingTests {
+
+ private final CorsConfigurationMapping mapping = new CorsConfigurationMapping();
+
+ @Test
+ public void empty() {
+ assertNull(this.mapping.getCorsConfiguration(new MockHttpServletRequest(HttpMethod.GET.name(), "/bar/test.html")));
+ }
+
+ @Test
+ public void registerAndMatch() {
+ CorsConfiguration config = new CorsConfiguration();
+ this.mapping.registerCorsConfiguration("/bar/**", config);
+ assertNull(this.mapping.getCorsConfiguration(new MockHttpServletRequest(HttpMethod.GET.name(), "/foo/test.html")));
+ assertEquals(config, this.mapping.getCorsConfiguration(new MockHttpServletRequest(HttpMethod.GET.name(), "/bar/test.html")));
+ }
+
+ @Test(expected = UnsupportedOperationException.class)
+ public void unmodifiableConfigurationsMap() {
+ this.mapping.getCorsConfigurations().put("/**", new CorsConfiguration());
+ }
+
+}
diff --git a/spring-web/src/test/java/org/springframework/web/filter/CorsFilterTests.java b/spring-web/src/test/java/org/springframework/web/filter/CorsFilterTests.java
new file mode 100644
index 0000000000..5945010867
--- /dev/null
+++ b/spring-web/src/test/java/org/springframework/web/filter/CorsFilterTests.java
@@ -0,0 +1,120 @@
+/*
+ * 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.filter;
+
+import java.io.IOException;
+import java.util.Arrays;
+import javax.servlet.FilterChain;
+import javax.servlet.ServletException;
+
+import static org.junit.Assert.*;
+import org.junit.Before;
+import org.junit.Test;
+
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.HttpMethod;
+import org.springframework.mock.web.test.MockHttpServletRequest;
+import org.springframework.mock.web.test.MockHttpServletResponse;
+import org.springframework.web.cors.CorsConfiguration;
+
+/**
+ * Unit tests for {@link CorsFilter}.
+ * @author Sebastien Deleuze
+ */
+public class CorsFilterTests {
+
+ private CorsFilter filter;
+
+ private final CorsConfiguration config = new CorsConfiguration();
+
+ @Before
+ public void setup() throws Exception {
+ config.setAllowedOrigins(Arrays.asList("http://domain1.com", "http://domain2.com"));
+ config.setAllowedMethods(Arrays.asList("GET", "POST"));
+ config.setAllowedHeaders(Arrays.asList("header1", "header2"));
+ config.setExposedHeaders(Arrays.asList("header3", "header4"));
+ config.setMaxAge(123L);
+ config.setAllowCredentials(false);
+ filter = new CorsFilter(r -> config);
+ }
+
+ @Test
+ public void validActualRequest() throws ServletException, IOException {
+
+ MockHttpServletRequest request = new MockHttpServletRequest(HttpMethod.GET.name(), "/test.html");
+ request.addHeader(HttpHeaders.ORIGIN, "http://domain2.com");
+ request.addHeader("header2", "foo");
+ MockHttpServletResponse response = new MockHttpServletResponse();
+
+ FilterChain filterChain = (filterRequest, filterResponse) -> {
+ assertEquals("http://domain2.com", response.getHeader(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN));
+ assertEquals("header3, header4", response.getHeader(HttpHeaders.ACCESS_CONTROL_EXPOSE_HEADERS));
+ };
+ filter.doFilter(request, response, filterChain);
+ }
+
+ @Test
+ public void invalidActualRequest() throws ServletException, IOException {
+
+ MockHttpServletRequest request = new MockHttpServletRequest(HttpMethod.DELETE.name(), "/test.html");
+ request.addHeader(HttpHeaders.ORIGIN, "http://domain2.com");
+ request.addHeader("header2", "foo");
+ MockHttpServletResponse response = new MockHttpServletResponse();
+
+ FilterChain filterChain = (filterRequest, filterResponse) -> {
+ fail("Invalid requests must not be forwarded to the filter chain");
+ };
+ filter.doFilter(request, response, filterChain);
+ assertNull(response.getHeader(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN));
+ }
+
+ @Test
+ public void validPreFlightRequest() throws ServletException, IOException {
+
+ MockHttpServletRequest request = new MockHttpServletRequest(HttpMethod.OPTIONS.name(), "/test.html");
+ request.addHeader(HttpHeaders.ORIGIN, "http://domain2.com");
+ request.addHeader(HttpHeaders.ACCESS_CONTROL_REQUEST_METHOD, HttpMethod.GET.name());
+ request.addHeader(HttpHeaders.ACCESS_CONTROL_REQUEST_HEADERS, "header1, header2");
+ MockHttpServletResponse response = new MockHttpServletResponse();
+
+ FilterChain filterChain = (filterRequest, filterResponse) ->
+ fail("Preflight requests must not be forwarded to the filter chain");
+ filter.doFilter(request, response, filterChain);
+
+ assertEquals("http://domain2.com", response.getHeader(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN));
+ assertEquals("header1, header2", response.getHeader(HttpHeaders.ACCESS_CONTROL_ALLOW_HEADERS));
+ assertEquals("header3, header4", response.getHeader(HttpHeaders.ACCESS_CONTROL_EXPOSE_HEADERS));
+ assertEquals(123L, Long.parseLong(response.getHeader(HttpHeaders.ACCESS_CONTROL_MAX_AGE)));
+ }
+
+ @Test
+ public void invalidPreFlightRequest() throws ServletException, IOException {
+
+ MockHttpServletRequest request = new MockHttpServletRequest(HttpMethod.OPTIONS.name(), "/test.html");
+ request.addHeader(HttpHeaders.ORIGIN, "http://domain2.com");
+ request.addHeader(HttpHeaders.ACCESS_CONTROL_REQUEST_METHOD, HttpMethod.DELETE.name());
+ request.addHeader(HttpHeaders.ACCESS_CONTROL_REQUEST_HEADERS, "header1, header2");
+ MockHttpServletResponse response = new MockHttpServletResponse();
+
+ FilterChain filterChain = (filterRequest, filterResponse) ->
+ fail("Preflight requests must not be forwarded to the filter chain");
+ filter.doFilter(request, response, filterChain);
+
+ assertNull(response.getHeader(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN));
+ }
+
+}
diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/config/AnnotationDrivenBeanDefinitionParser.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/config/AnnotationDrivenBeanDefinitionParser.java
index c7c78a9621..ac4873e3fc 100644
--- a/spring-webmvc/src/main/java/org/springframework/web/servlet/config/AnnotationDrivenBeanDefinitionParser.java
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/config/AnnotationDrivenBeanDefinitionParser.java
@@ -197,8 +197,8 @@ class AnnotationDrivenBeanDefinitionParser implements BeanDefinitionParser {
configurePathMatchingProperties(handlerMappingDef, element, parserContext);
- RuntimeBeanReference corsConfigurationRef = MvcNamespaceUtils.registerCorsConfiguration(null, parserContext, source);
- handlerMappingDef.getPropertyValues().add("corsConfiguration", corsConfigurationRef);
+ RuntimeBeanReference corsConfigurationsRef = MvcNamespaceUtils.registerCorsConfigurations(null, parserContext, source);
+ handlerMappingDef.getPropertyValues().add("corsConfigurations", corsConfigurationsRef);
RuntimeBeanReference conversionService = getConversionService(element, source, parserContext);
RuntimeBeanReference validator = getValidator(element, source, parserContext);
diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/config/CorsBeanDefinitionParser.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/config/CorsBeanDefinitionParser.java
index fa70a9e7b9..c62f70dcdf 100644
--- a/spring-webmvc/src/main/java/org/springframework/web/servlet/config/CorsBeanDefinitionParser.java
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/config/CorsBeanDefinitionParser.java
@@ -113,7 +113,7 @@ public class CorsBeanDefinitionParser implements BeanDefinitionParser {
}
}
- MvcNamespaceUtils.registerCorsConfiguration(corsConfigurations, parserContext, parserContext.extractSource(element));
+ MvcNamespaceUtils.registerCorsConfigurations(corsConfigurations, parserContext, parserContext.extractSource(element));
return null;
}
diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/config/MvcNamespaceUtils.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/config/MvcNamespaceUtils.java
index 5b36371d4c..e668413688 100644
--- a/spring-webmvc/src/main/java/org/springframework/web/servlet/config/MvcNamespaceUtils.java
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/config/MvcNamespaceUtils.java
@@ -119,8 +119,8 @@ abstract class MvcNamespaceUtils {
beanNameMappingDef.setSource(source);
beanNameMappingDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
beanNameMappingDef.getPropertyValues().add("order", 2); // consistent with WebMvcConfigurationSupport
- RuntimeBeanReference corsConfigurationRef = MvcNamespaceUtils.registerCorsConfiguration(null, parserContext, source);
- beanNameMappingDef.getPropertyValues().add("corsConfiguration", corsConfigurationRef);
+ RuntimeBeanReference corsConfigurationsRef = MvcNamespaceUtils.registerCorsConfigurations(null, parserContext, source);
+ beanNameMappingDef.getPropertyValues().add("corsConfigurations", corsConfigurationsRef);
parserContext.getRegistry().registerBeanDefinition(BEAN_NAME_URL_HANDLER_MAPPING_BEAN_NAME, beanNameMappingDef);
parserContext.registerComponent(new BeanComponentDefinition(beanNameMappingDef, BEAN_NAME_URL_HANDLER_MAPPING_BEAN_NAME));
}
@@ -160,20 +160,20 @@ abstract class MvcNamespaceUtils {
* if a non-null CORS configuration is provided.
* @return a RuntimeBeanReference to this {@code Map} instance
*/
- public static RuntimeBeanReference registerCorsConfiguration(Map corsConfiguration, ParserContext parserContext, Object source) {
+ public static RuntimeBeanReference registerCorsConfigurations(Map corsConfigurations, ParserContext parserContext, Object source) {
if (!parserContext.getRegistry().containsBeanDefinition(CORS_CONFIGURATION_BEAN_NAME)) {
RootBeanDefinition corsConfigurationsDef = new RootBeanDefinition(LinkedHashMap.class);
corsConfigurationsDef.setSource(source);
corsConfigurationsDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
- if (corsConfiguration != null) {
- corsConfigurationsDef.getConstructorArgumentValues().addIndexedArgumentValue(0, corsConfiguration);
+ if (corsConfigurations != null) {
+ corsConfigurationsDef.getConstructorArgumentValues().addIndexedArgumentValue(0, corsConfigurations);
}
parserContext.getReaderContext().getRegistry().registerBeanDefinition(CORS_CONFIGURATION_BEAN_NAME, corsConfigurationsDef);
parserContext.registerComponent(new BeanComponentDefinition(corsConfigurationsDef, CORS_CONFIGURATION_BEAN_NAME));
}
- else if (corsConfiguration != null) {
+ else if (corsConfigurations != null) {
BeanDefinition corsConfigurationsDef = parserContext.getRegistry().getBeanDefinition(CORS_CONFIGURATION_BEAN_NAME);
- corsConfigurationsDef.getConstructorArgumentValues().addIndexedArgumentValue(0, corsConfiguration);
+ corsConfigurationsDef.getConstructorArgumentValues().addIndexedArgumentValue(0, corsConfigurations);
}
return new RuntimeBeanReference(CORS_CONFIGURATION_BEAN_NAME);
}
diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/config/ResourcesBeanDefinitionParser.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/config/ResourcesBeanDefinitionParser.java
index 9ad4c4ce98..8e008ba131 100644
--- a/spring-webmvc/src/main/java/org/springframework/web/servlet/config/ResourcesBeanDefinitionParser.java
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/config/ResourcesBeanDefinitionParser.java
@@ -116,8 +116,8 @@ class ResourcesBeanDefinitionParser implements BeanDefinitionParser {
// Use a default of near-lowest precedence, still allowing for even lower precedence in other mappings
handlerMappingDef.getPropertyValues().add("order", StringUtils.hasText(order) ? order : Ordered.LOWEST_PRECEDENCE - 1);
- RuntimeBeanReference corsConfigurationRef = MvcNamespaceUtils.registerCorsConfiguration(null, parserContext, source);
- handlerMappingDef.getPropertyValues().add("corsConfiguration", corsConfigurationRef);
+ RuntimeBeanReference corsConfigurationsRef = MvcNamespaceUtils.registerCorsConfigurations(null, parserContext, source);
+ handlerMappingDef.getPropertyValues().add("corsConfigurations", corsConfigurationsRef);
String beanName = parserContext.getReaderContext().generateBeanName(handlerMappingDef);
parserContext.getRegistry().registerBeanDefinition(beanName, handlerMappingDef);
diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/config/ViewControllerBeanDefinitionParser.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/config/ViewControllerBeanDefinitionParser.java
index 2843435784..af0946469b 100644
--- a/spring-webmvc/src/main/java/org/springframework/web/servlet/config/ViewControllerBeanDefinitionParser.java
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/config/ViewControllerBeanDefinitionParser.java
@@ -127,8 +127,8 @@ class ViewControllerBeanDefinitionParser implements BeanDefinitionParser {
beanDef.getPropertyValues().add("order", "1");
beanDef.getPropertyValues().add("pathMatcher", MvcNamespaceUtils.registerPathMatcher(null, context, source));
beanDef.getPropertyValues().add("urlPathHelper", MvcNamespaceUtils.registerUrlPathHelper(null, context, source));
- RuntimeBeanReference corsConfigurationRef = MvcNamespaceUtils.registerCorsConfiguration(null, context, source);
- beanDef.getPropertyValues().add("corsConfiguration", corsConfigurationRef);
+ RuntimeBeanReference corsConfigurationsRef = MvcNamespaceUtils.registerCorsConfigurations(null, context, source);
+ beanDef.getPropertyValues().add("corsConfigurations", corsConfigurationsRef);
return beanDef;
}
diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/config/annotation/WebMvcConfigurationSupport.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/config/annotation/WebMvcConfigurationSupport.java
index 65dd5fd9aa..a34b886413 100644
--- a/spring-webmvc/src/main/java/org/springframework/web/servlet/config/annotation/WebMvcConfigurationSupport.java
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/config/annotation/WebMvcConfigurationSupport.java
@@ -239,7 +239,7 @@ public class WebMvcConfigurationSupport implements ApplicationContextAware, Serv
handlerMapping.setOrder(0);
handlerMapping.setInterceptors(getInterceptors());
handlerMapping.setContentNegotiationManager(mvcContentNegotiationManager());
- handlerMapping.setCorsConfiguration(getCorsConfigurations());
+ handlerMapping.setCorsConfigurations(getCorsConfigurations());
PathMatchConfigurer configurer = getPathMatchConfigurer();
if (configurer.isUseSuffixPatternMatch() != null) {
@@ -371,7 +371,7 @@ public class WebMvcConfigurationSupport implements ApplicationContextAware, Serv
handlerMapping.setPathMatcher(mvcPathMatcher());
handlerMapping.setUrlPathHelper(mvcUrlPathHelper());
handlerMapping.setInterceptors(getInterceptors());
- handlerMapping.setCorsConfiguration(getCorsConfigurations());
+ handlerMapping.setCorsConfigurations(getCorsConfigurations());
return handlerMapping;
}
@@ -391,7 +391,7 @@ public class WebMvcConfigurationSupport implements ApplicationContextAware, Serv
BeanNameUrlHandlerMapping mapping = new BeanNameUrlHandlerMapping();
mapping.setOrder(2);
mapping.setInterceptors(getInterceptors());
- mapping.setCorsConfiguration(getCorsConfigurations());
+ mapping.setCorsConfigurations(getCorsConfigurations());
return mapping;
}
@@ -411,7 +411,7 @@ public class WebMvcConfigurationSupport implements ApplicationContextAware, Serv
handlerMapping.setUrlPathHelper(mvcUrlPathHelper());
handlerMapping.setInterceptors(new HandlerInterceptor[] {
new ResourceUrlProviderExposingInterceptor(mvcResourceUrlProvider())});
- handlerMapping.setCorsConfiguration(getCorsConfigurations());
+ handlerMapping.setCorsConfigurations(getCorsConfigurations());
}
else {
handlerMapping = new EmptyHandlerMapping();
diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/handler/AbstractHandlerMapping.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/handler/AbstractHandlerMapping.java
index d096216fb1..23cf5b78c0 100644
--- a/spring-webmvc/src/main/java/org/springframework/web/servlet/handler/AbstractHandlerMapping.java
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/handler/AbstractHandlerMapping.java
@@ -19,7 +19,6 @@ package org.springframework.web.servlet.handler;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
-import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
@@ -29,6 +28,7 @@ import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactoryUtils;
import org.springframework.core.Ordered;
import org.springframework.web.HttpRequestHandler;
+import org.springframework.web.cors.CorsConfigurationMapping;
import org.springframework.web.cors.CorsProcessor;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.CorsConfigurationSource;
@@ -81,8 +81,7 @@ public abstract class AbstractHandlerMapping extends WebApplicationObjectSupport
private CorsProcessor corsProcessor = new DefaultCorsProcessor();
- private final Map corsConfiguration =
- new LinkedHashMap();
+ private final CorsConfigurationMapping corsMapping = new CorsConfigurationMapping();
/**
@@ -125,6 +124,7 @@ public abstract class AbstractHandlerMapping extends WebApplicationObjectSupport
*/
public void setAlwaysUseFullPath(boolean alwaysUseFullPath) {
this.urlPathHelper.setAlwaysUseFullPath(alwaysUseFullPath);
+ this.corsMapping.setAlwaysUseFullPath(alwaysUseFullPath);
}
/**
@@ -136,6 +136,7 @@ public abstract class AbstractHandlerMapping extends WebApplicationObjectSupport
*/
public void setUrlDecode(boolean urlDecode) {
this.urlPathHelper.setUrlDecode(urlDecode);
+ this.corsMapping.setUrlDecode(urlDecode);
}
/**
@@ -145,6 +146,7 @@ public abstract class AbstractHandlerMapping extends WebApplicationObjectSupport
*/
public void setRemoveSemicolonContent(boolean removeSemicolonContent) {
this.urlPathHelper.setRemoveSemicolonContent(removeSemicolonContent);
+ this.corsMapping.setRemoveSemicolonContent(removeSemicolonContent);
}
/**
@@ -156,6 +158,7 @@ public abstract class AbstractHandlerMapping extends WebApplicationObjectSupport
public void setUrlPathHelper(UrlPathHelper urlPathHelper) {
Assert.notNull(urlPathHelper, "UrlPathHelper must not be null");
this.urlPathHelper = urlPathHelper;
+ this.corsMapping.setUrlPathHelper(urlPathHelper);
}
/**
@@ -173,6 +176,7 @@ public abstract class AbstractHandlerMapping extends WebApplicationObjectSupport
public void setPathMatcher(PathMatcher pathMatcher) {
Assert.notNull(pathMatcher, "PathMatcher must not be null");
this.pathMatcher = pathMatcher;
+ this.corsMapping.setPathMatcher(pathMatcher);
}
/**
@@ -221,18 +225,15 @@ public abstract class AbstractHandlerMapping extends WebApplicationObjectSupport
* handler, if any.
* @since 4.2
*/
- public void setCorsConfiguration(Map corsConfiguration) {
- this.corsConfiguration.clear();
- if (corsConfiguration != null) {
- this.corsConfiguration.putAll(corsConfiguration);
- }
+ public void setCorsConfigurations(Map corsConfigurations) {
+ this.corsMapping.setCorsConfigurations(corsConfigurations);
}
/**
* Get the CORS configuration.
*/
- public Map getCorsConfiguration() {
- return this.corsConfiguration;
+ public Map getCorsConfigurations() {
+ return this.corsMapping.getCorsConfigurations();
}
/**
@@ -361,7 +362,7 @@ public abstract class AbstractHandlerMapping extends WebApplicationObjectSupport
}
HandlerExecutionChain executionChain = getHandlerExecutionChain(handler, request);
if (CorsUtils.isCorsRequest(request)) {
- CorsConfiguration globalConfig = getCorsConfiguration(request);
+ CorsConfiguration globalConfig = this.corsMapping.getCorsConfiguration(request);
CorsConfiguration handlerConfig = getCorsConfiguration(handler, request);
CorsConfiguration config = (globalConfig != null ? globalConfig.combine(handlerConfig) : handlerConfig);
executionChain = getCorsHandlerExecutionChain(request, executionChain, config);
@@ -378,7 +379,7 @@ public abstract class AbstractHandlerMapping extends WebApplicationObjectSupport
* the pre-flight request but for the expected actual request based on the URL
* path, the HTTP methods from the "Access-Control-Request-Method" header, and
* the headers from the "Access-Control-Request-Headers" header thus allowing
- * the CORS configuration to be obtained via {@link #getCorsConfiguration},
+ * the CORS configuration to be obtained via {@link #getCorsConfigurations},
*
* Note: This method may also return a pre-built {@link HandlerExecutionChain},
* combining a handler object with dynamically determined interceptors.
@@ -429,22 +430,6 @@ public abstract class AbstractHandlerMapping extends WebApplicationObjectSupport
return chain;
}
- /**
- * Find the "global" CORS configuration for the given URL configured via
- * {@link #setCorsConfiguration(Map)}.
- * @param request the request
- * @return the CORS configuration or {@code null}
- */
- protected CorsConfiguration getCorsConfiguration(HttpServletRequest request) {
- String lookupPath = getUrlPathHelper().getLookupPathForRequest(request);
- for(Map.Entry entry : getCorsConfiguration().entrySet()) {
- if (getPathMatcher().match(entry.getKey(), lookupPath)) {
- return entry.getValue();
- }
- }
- return null;
- }
-
/**
* Retrieve the CORS configuration for the given handler.
* @param handler the handler to check (never {@code null}).
diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/config/MvcNamespaceTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/config/MvcNamespaceTests.java
index dca9ce9230..fe7ba2f77f 100644
--- a/spring-webmvc/src/test/java/org/springframework/web/servlet/config/MvcNamespaceTests.java
+++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/config/MvcNamespaceTests.java
@@ -875,7 +875,7 @@ public class MvcNamespaceTests {
for (String beanName : beanNames) {
AbstractHandlerMapping handlerMapping = (AbstractHandlerMapping)appContext.getBean(beanName);
assertNotNull(handlerMapping);
- Map configs = handlerMapping.getCorsConfiguration();
+ Map configs = handlerMapping.getCorsConfigurations();
assertNotNull(configs);
assertEquals(1, configs.size());
CorsConfiguration config = configs.get("/**");
@@ -898,7 +898,7 @@ public class MvcNamespaceTests {
for (String beanName : beanNames) {
AbstractHandlerMapping handlerMapping = (AbstractHandlerMapping)appContext.getBean(beanName);
assertNotNull(handlerMapping);
- Map configs = handlerMapping.getCorsConfiguration();
+ Map configs = handlerMapping.getCorsConfigurations();
assertNotNull(configs);
assertEquals(2, configs.size());
CorsConfiguration config = configs.get("/api/**");
diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/handler/CorsAbstractHandlerMappingTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/handler/CorsAbstractHandlerMappingTests.java
index 31fece4e53..0a6c680ee7 100644
--- a/spring-webmvc/src/test/java/org/springframework/web/servlet/handler/CorsAbstractHandlerMappingTests.java
+++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/handler/CorsAbstractHandlerMappingTests.java
@@ -118,7 +118,7 @@ public class CorsAbstractHandlerMappingTests {
public void actualRequestWithMappedCorsConfiguration() throws Exception {
CorsConfiguration config = new CorsConfiguration();
config.addAllowedOrigin("*");
- this.handlerMapping.setCorsConfiguration(Collections.singletonMap("/foo", config));
+ this.handlerMapping.setCorsConfigurations(Collections.singletonMap("/foo", config));
this.request.setMethod(RequestMethod.GET.name());
this.request.setRequestURI("/foo");
this.request.addHeader(HttpHeaders.ORIGIN, "http://domain2.com");
@@ -135,7 +135,7 @@ public class CorsAbstractHandlerMappingTests {
public void preflightRequestWithMappedCorsConfiguration() throws Exception {
CorsConfiguration config = new CorsConfiguration();
config.addAllowedOrigin("*");
- this.handlerMapping.setCorsConfiguration(Collections.singletonMap("/foo", config));
+ this.handlerMapping.setCorsConfigurations(Collections.singletonMap("/foo", config));
this.request.setMethod(RequestMethod.OPTIONS.name());
this.request.setRequestURI("/foo");
this.request.addHeader(HttpHeaders.ORIGIN, "http://domain2.com");