Introduce CorsFilter and CorsConfigurationMapping

This commit introduces the following changes:
 - The new CorsConfigurationMapping class allows to share the mapped
   CorsConfiguration logic between AbstractHandlerMapping and CorsFilter
 - In AbstractHandlerMapping, the Map<String, CorsConfiguration>
   corsConfiguration property has been renamed to corsConfigurations
 - CorsFilter allows to process CORS requests at filter level, using any
   CorsConfigurationSource implementation (for example
   CorsConfigurationMapping)

Issue: SPR-13192
This commit is contained in:
Sebastien Deleuze
2015-07-09 22:19:46 +02:00
parent df9290c00d
commit cd9b3903a7
13 changed files with 429 additions and 50 deletions

View File

@@ -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.
*
* <p>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<String, CorsConfiguration> corsConfigurations =
new LinkedHashMap<String, CorsConfiguration>();
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).
* <p>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
* <i>undecoded</i> by the Servlet API, in contrast to the servlet path.
* <p>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.
* <p>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.
* <p>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<String, CorsConfiguration> corsConfigurations) {
this.corsConfigurations.clear();
if (corsConfigurations != null) {
this.corsConfigurations.putAll(corsConfigurations);
}
}
/**
* Get the CORS configuration.
*/
public Map<String, CorsConfiguration> 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<String, CorsConfiguration> entry : this.corsConfigurations.entrySet()) {
if (this.pathMatcher.match(entry.getKey(), lookupPath)) {
return entry.getValue();
}
}
return null;
}
}

View File

@@ -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.
*
* <p>This filter could be used in conjunction with {@link DelegatingFilterProxy} in order
* to help with its initialization.
*
* @author Sebastien Deleuze
* @since 4.2
* @see <a href="http://www.w3.org/TR/cors/">CORS W3C recommendation</a>
*/
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.
* <p>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);
}
}

View File

@@ -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());
}
}

View File

@@ -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));
}
}

View File

@@ -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);

View File

@@ -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;
}

View File

@@ -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<String, CorsConfiguration>} instance
*/
public static RuntimeBeanReference registerCorsConfiguration(Map<String, CorsConfiguration> corsConfiguration, ParserContext parserContext, Object source) {
public static RuntimeBeanReference registerCorsConfigurations(Map<String, CorsConfiguration> 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);
}

View File

@@ -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);

View File

@@ -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;
}

View File

@@ -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();

View File

@@ -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<String, CorsConfiguration> corsConfiguration =
new LinkedHashMap<String, CorsConfiguration>();
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<String, CorsConfiguration> corsConfiguration) {
this.corsConfiguration.clear();
if (corsConfiguration != null) {
this.corsConfiguration.putAll(corsConfiguration);
}
public void setCorsConfigurations(Map<String, CorsConfiguration> corsConfigurations) {
this.corsMapping.setCorsConfigurations(corsConfigurations);
}
/**
* Get the CORS configuration.
*/
public Map<String, CorsConfiguration> getCorsConfiguration() {
return this.corsConfiguration;
public Map<String, CorsConfiguration> 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},
*
* <p>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<String, CorsConfiguration> 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}).

View File

@@ -875,7 +875,7 @@ public class MvcNamespaceTests {
for (String beanName : beanNames) {
AbstractHandlerMapping handlerMapping = (AbstractHandlerMapping)appContext.getBean(beanName);
assertNotNull(handlerMapping);
Map<String, CorsConfiguration> configs = handlerMapping.getCorsConfiguration();
Map<String, CorsConfiguration> 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<String, CorsConfiguration> configs = handlerMapping.getCorsConfiguration();
Map<String, CorsConfiguration> configs = handlerMapping.getCorsConfigurations();
assertNotNull(configs);
assertEquals(2, configs.size());
CorsConfiguration config = configs.get("/api/**");

View File

@@ -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");