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

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