Support CORS global configuration in XML namespace
This commit introduces support for this kind of CORS XML namespace configuration: <mvc:cors> <mvc:mapping path="/api/**" allowed-origins="http://domain1.com, http://domain2.com" allowed-methods="GET, PUT" allowed-headers="header1, header2, header3" exposed-headers="header1, header2" allow-credentials="false" max-age="123" /> <mvc:mapping path="/resources/**" allowed-origins="http://domain1.com" /> </mvc:cors> Issue: SPR-13046
This commit is contained in:
@@ -197,6 +197,9 @@ class AnnotationDrivenBeanDefinitionParser implements BeanDefinitionParser {
|
||||
|
||||
configurePathMatchingProperties(handlerMappingDef, element, parserContext);
|
||||
|
||||
RuntimeBeanReference corsConfigurationRef = MvcNamespaceUtils.registerCorsConfiguration(null, parserContext, source);
|
||||
handlerMappingDef.getPropertyValues().add("corsConfiguration", corsConfigurationRef);
|
||||
|
||||
RuntimeBeanReference conversionService = getConversionService(element, source, parserContext);
|
||||
RuntimeBeanReference validator = getValidator(element, source, parserContext);
|
||||
RuntimeBeanReference messageCodesResolver = getMessageCodesResolver(element);
|
||||
|
||||
@@ -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.servlet.config;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.w3c.dom.Element;
|
||||
|
||||
import org.springframework.beans.factory.config.BeanDefinition;
|
||||
import org.springframework.beans.factory.xml.BeanDefinitionParser;
|
||||
import org.springframework.beans.factory.xml.ParserContext;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.util.xml.DomUtils;
|
||||
import org.springframework.web.cors.CorsConfiguration;
|
||||
|
||||
/**
|
||||
* {@link org.springframework.beans.factory.xml.BeanDefinitionParser} that parses a
|
||||
* {@code cors} element in order to set the CORS configuration in the various
|
||||
* {AbstractHandlerMapping} beans created by {@link AnnotationDrivenBeanDefinitionParser},
|
||||
* {@link ResourcesBeanDefinitionParser} and {@link ViewControllerBeanDefinitionParser}.
|
||||
*
|
||||
* @author Sebastien Deleuze
|
||||
* @since 4.2
|
||||
*/
|
||||
public class CorsBeanDefinitionParser implements BeanDefinitionParser {
|
||||
|
||||
private static final List<String> DEFAULT_ALLOWED_ORIGINS = Arrays.asList("*");
|
||||
|
||||
private static final List<String> DEFAULT_ALLOWED_METHODS =
|
||||
Arrays.asList(HttpMethod.GET.name(), HttpMethod.HEAD.name(), HttpMethod.POST.name());
|
||||
|
||||
private static final List<String> DEFAULT_ALLOWED_HEADERS = Arrays.asList("*");
|
||||
|
||||
private static final boolean DEFAULT_ALLOW_CREDENTIALS = true;
|
||||
|
||||
private static final long DEFAULT_MAX_AGE = 1600;
|
||||
|
||||
|
||||
@Override
|
||||
public BeanDefinition parse(Element element, ParserContext parserContext) {
|
||||
|
||||
Map<String, CorsConfiguration> corsConfigurations = new LinkedHashMap<String, CorsConfiguration>();
|
||||
List<Element> mappings = DomUtils.getChildElementsByTagName(element, "mapping");
|
||||
|
||||
if (mappings.isEmpty()) {
|
||||
CorsConfiguration config = new CorsConfiguration();
|
||||
config.setAllowedOrigins(DEFAULT_ALLOWED_ORIGINS);
|
||||
config.setAllowedMethods(DEFAULT_ALLOWED_METHODS);
|
||||
config.setAllowedHeaders(DEFAULT_ALLOWED_HEADERS);
|
||||
config.setAllowCredentials(DEFAULT_ALLOW_CREDENTIALS);
|
||||
config.setMaxAge(DEFAULT_MAX_AGE);
|
||||
corsConfigurations.put("/**", config);
|
||||
}
|
||||
else {
|
||||
for (Element mapping : mappings) {
|
||||
CorsConfiguration config = new CorsConfiguration();
|
||||
if (mapping.hasAttribute("allowed-origins")) {
|
||||
String[] allowedOrigins = StringUtils.tokenizeToStringArray(mapping.getAttribute("allowed-origins"), ",");
|
||||
config.setAllowedOrigins(Arrays.asList(allowedOrigins));
|
||||
}
|
||||
else {
|
||||
config.setAllowedOrigins(DEFAULT_ALLOWED_ORIGINS);
|
||||
}
|
||||
if (mapping.hasAttribute("allowed-methods")) {
|
||||
String[] allowedMethods = StringUtils.tokenizeToStringArray(mapping.getAttribute("allowed-methods"), ",");
|
||||
config.setAllowedMethods(Arrays.asList(allowedMethods));
|
||||
}
|
||||
else {
|
||||
config.setAllowedMethods(DEFAULT_ALLOWED_METHODS);
|
||||
}
|
||||
if (mapping.hasAttribute("allowed-headers")) {
|
||||
String[] allowedHeaders = StringUtils.tokenizeToStringArray(mapping.getAttribute("allowed-headers"), ",");
|
||||
config.setAllowedHeaders(Arrays.asList(allowedHeaders));
|
||||
}
|
||||
else {
|
||||
config.setAllowedHeaders(DEFAULT_ALLOWED_HEADERS);
|
||||
}
|
||||
if (mapping.hasAttribute("exposed-headers")) {
|
||||
String[] exposedHeaders = StringUtils.tokenizeToStringArray(mapping.getAttribute("exposed-headers"), ",");
|
||||
config.setExposedHeaders(Arrays.asList(exposedHeaders));
|
||||
}
|
||||
if (mapping.hasAttribute("allow-credentials")) {
|
||||
config.setAllowCredentials(Boolean.parseBoolean(mapping.getAttribute("allow-credentials")));
|
||||
}
|
||||
else {
|
||||
config.setAllowCredentials(DEFAULT_ALLOW_CREDENTIALS);
|
||||
}
|
||||
if (mapping.hasAttribute("max-age")) {
|
||||
config.setMaxAge(Long.parseLong(mapping.getAttribute("max-age")));
|
||||
}
|
||||
else {
|
||||
config.setMaxAge(DEFAULT_MAX_AGE);
|
||||
}
|
||||
corsConfigurations.put(mapping.getAttribute("path"), config);
|
||||
}
|
||||
}
|
||||
|
||||
MvcNamespaceUtils.registerCorsConfiguration(corsConfigurations, parserContext, parserContext.extractSource(element));
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -44,6 +44,7 @@ public class MvcNamespaceHandler extends NamespaceHandlerSupport {
|
||||
registerBeanDefinitionParser("velocity-configurer", new VelocityConfigurerBeanDefinitionParser());
|
||||
registerBeanDefinitionParser("groovy-configurer", new GroovyMarkupConfigurerBeanDefinitionParser());
|
||||
registerBeanDefinitionParser("script-template-configurer", new ScriptTemplateConfigurerBeanDefinitionParser());
|
||||
registerBeanDefinitionParser("cors", new CorsBeanDefinitionParser());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -16,6 +16,9 @@
|
||||
|
||||
package org.springframework.web.servlet.config;
|
||||
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.beans.factory.config.BeanDefinition;
|
||||
import org.springframework.beans.factory.config.RuntimeBeanReference;
|
||||
import org.springframework.beans.factory.parsing.BeanComponentDefinition;
|
||||
@@ -23,6 +26,7 @@ import org.springframework.beans.factory.support.RootBeanDefinition;
|
||||
import org.springframework.beans.factory.xml.ParserContext;
|
||||
import org.springframework.util.AntPathMatcher;
|
||||
import org.springframework.util.PathMatcher;
|
||||
import org.springframework.web.cors.CorsConfiguration;
|
||||
import org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping;
|
||||
import org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter;
|
||||
import org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter;
|
||||
@@ -50,6 +54,8 @@ abstract class MvcNamespaceUtils {
|
||||
|
||||
private static final String PATH_MATCHER_BEAN_NAME = "mvcPathMatcher";
|
||||
|
||||
private static final String CORS_CONFIGURATION_BEAN_NAME = "mvcCorsConfigurations";
|
||||
|
||||
|
||||
public static void registerDefaultComponents(ParserContext parserContext, Object source) {
|
||||
registerBeanNameUrlHandlerMapping(parserContext, source);
|
||||
@@ -113,6 +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);
|
||||
parserContext.getRegistry().registerBeanDefinition(BEAN_NAME_URL_HANDLER_MAPPING_BEAN_NAME, beanNameMappingDef);
|
||||
parserContext.registerComponent(new BeanComponentDefinition(beanNameMappingDef, BEAN_NAME_URL_HANDLER_MAPPING_BEAN_NAME));
|
||||
}
|
||||
@@ -146,4 +154,28 @@ abstract class MvcNamespaceUtils {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a {@code Map<String, CorsConfiguration>} (mapped {@code CorsConfiguration}s)
|
||||
* under a well-known name unless already registered. The bean definition may be updated
|
||||
* 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) {
|
||||
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);
|
||||
}
|
||||
parserContext.getReaderContext().getRegistry().registerBeanDefinition(CORS_CONFIGURATION_BEAN_NAME, corsConfigurationsDef);
|
||||
parserContext.registerComponent(new BeanComponentDefinition(corsConfigurationsDef, CORS_CONFIGURATION_BEAN_NAME));
|
||||
}
|
||||
else if (corsConfiguration != null) {
|
||||
BeanDefinition corsConfigurationsDef = parserContext.getRegistry().getBeanDefinition(CORS_CONFIGURATION_BEAN_NAME);
|
||||
corsConfigurationsDef.getConstructorArgumentValues().addIndexedArgumentValue(0, corsConfiguration);
|
||||
}
|
||||
return new RuntimeBeanReference(CORS_CONFIGURATION_BEAN_NAME);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -116,6 +116,9 @@ 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);
|
||||
|
||||
String beanName = parserContext.getReaderContext().generateBeanName(handlerMappingDef);
|
||||
parserContext.getRegistry().registerBeanDefinition(beanName, handlerMappingDef);
|
||||
parserContext.registerComponent(new BeanComponentDefinition(handlerMappingDef, beanName));
|
||||
|
||||
@@ -22,6 +22,7 @@ import org.w3c.dom.Element;
|
||||
|
||||
import org.springframework.beans.factory.config.BeanDefinition;
|
||||
import org.springframework.beans.factory.config.ConstructorArgumentValues;
|
||||
import org.springframework.beans.factory.config.RuntimeBeanReference;
|
||||
import org.springframework.beans.factory.parsing.BeanComponentDefinition;
|
||||
import org.springframework.beans.factory.support.ManagedMap;
|
||||
import org.springframework.beans.factory.support.RootBeanDefinition;
|
||||
@@ -126,6 +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);
|
||||
|
||||
return beanDef;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user