Browse Source

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
pull/812/head
Sebastien Deleuze 11 years ago
parent
commit
e5f76af193
  1. 3
      spring-webmvc/src/main/java/org/springframework/web/servlet/config/AnnotationDrivenBeanDefinitionParser.java
  2. 120
      spring-webmvc/src/main/java/org/springframework/web/servlet/config/CorsBeanDefinitionParser.java
  3. 1
      spring-webmvc/src/main/java/org/springframework/web/servlet/config/MvcNamespaceHandler.java
  4. 32
      spring-webmvc/src/main/java/org/springframework/web/servlet/config/MvcNamespaceUtils.java
  5. 3
      spring-webmvc/src/main/java/org/springframework/web/servlet/config/ResourcesBeanDefinitionParser.java
  6. 3
      spring-webmvc/src/main/java/org/springframework/web/servlet/config/ViewControllerBeanDefinitionParser.java
  7. 83
      spring-webmvc/src/main/resources/org/springframework/web/servlet/config/spring-mvc-4.2.xsd
  8. 89
      spring-webmvc/src/test/java/org/springframework/web/servlet/config/MvcNamespaceTests.java
  9. 13
      spring-webmvc/src/test/resources/org/springframework/web/servlet/config/mvc-config-cors-minimal.xml
  10. 20
      spring-webmvc/src/test/resources/org/springframework/web/servlet/config/mvc-config-cors.xml

3
spring-webmvc/src/main/java/org/springframework/web/servlet/config/AnnotationDrivenBeanDefinitionParser.java

@ -197,6 +197,9 @@ class AnnotationDrivenBeanDefinitionParser implements BeanDefinitionParser { @@ -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);

120
spring-webmvc/src/main/java/org/springframework/web/servlet/config/CorsBeanDefinitionParser.java

@ -0,0 +1,120 @@ @@ -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;
}
}

1
spring-webmvc/src/main/java/org/springframework/web/servlet/config/MvcNamespaceHandler.java

@ -44,6 +44,7 @@ public class MvcNamespaceHandler extends NamespaceHandlerSupport { @@ -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());
}
}

32
spring-webmvc/src/main/java/org/springframework/web/servlet/config/MvcNamespaceUtils.java

@ -16,6 +16,9 @@ @@ -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; @@ -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 { @@ -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 { @@ -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 { @@ -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);
}
}

3
spring-webmvc/src/main/java/org/springframework/web/servlet/config/ResourcesBeanDefinitionParser.java

@ -116,6 +116,9 @@ class ResourcesBeanDefinitionParser implements BeanDefinitionParser { @@ -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));

3
spring-webmvc/src/main/java/org/springframework/web/servlet/config/ViewControllerBeanDefinitionParser.java

@ -22,6 +22,7 @@ import org.w3c.dom.Element; @@ -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 { @@ -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;
}

83
spring-webmvc/src/main/resources/org/springframework/web/servlet/config/spring-mvc-4.2.xsd

@ -1234,4 +1234,87 @@ @@ -1234,4 +1234,87 @@
</xsd:complexType>
</xsd:element>
<xsd:element name="cors">
<xsd:annotation>
<xsd:documentation><![CDATA[
Configure cross origin requests handling.
By default, all origins, GET HEAD POST methods, all headers and credentials
are allowed and max age is set to 30 minutes.
]]></xsd:documentation>
</xsd:annotation>
<xsd:complexType>
<xsd:sequence>
<xsd:element name="mapping" minOccurs="0" maxOccurs="unbounded">
<xsd:annotation>
<xsd:documentation><![CDATA[
Enable cross origin requests handling on the specified path patterns.
By default, all origins, GET HEAD POST methods, all headers and credentials
are allowed and max age is set to 30 minutes.
]]></xsd:documentation>
</xsd:annotation>
<xsd:complexType>
<xsd:attribute name="path" type="xsd:string" use="required">
<xsd:annotation>
<xsd:documentation><![CDATA[
A path into the application that should handle CORS requests.
Exact path mapping URIs (such as "/admin") are supported as well as Ant-stype path patterns (such as /admin/**).
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="allowed-origins" type="xsd:string">
<xsd:annotation>
<xsd:documentation><![CDATA[
Comma-separated list of origins to allow, e.g. "http://domain1.com, http://domain2.com".
The special value "*" allows all domains (default).
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="allowed-methods" type="xsd:string">
<xsd:annotation>
<xsd:documentation><![CDATA[
Comma-separated list of HTTP methods to allow, e.g. "GET, POST".
The special value "*" allows all method.
By default GET, HEAD and POST methods are allowed.
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="allowed-headers" type="xsd:string">
<xsd:annotation>
<xsd:documentation><![CDATA[
Comma-separated list of headers that a pre-flight request can list as allowed for use during an actual request.
The special value of "*" allows actual requests to send any header (default).
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="exposed-headers" type="xsd:string">
<xsd:annotation>
<xsd:documentation><![CDATA[
Comma-separated list of response headers other than simple headers (i.e.
Cache-Control, Content-Language, Content-Type, Expires, Last-Modified, Pragma) that an
actual response might have and can be exposed.
Empty by default.
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="allow-credentials" type="xsd:boolean">
<xsd:annotation>
<xsd:documentation><![CDATA[
Whether user credentials are supported (true by default).
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="max-age" type="xsd:long">
<xsd:annotation>
<xsd:documentation><![CDATA[
How long, in seconds, the response from a pre-flight request can be cached by clients.
1800 seconds (30 minutes) by default.
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
</xsd:complexType>
</xsd:element>
</xsd:sequence>
</xsd:complexType>
</xsd:element>
</xsd:schema>

89
spring-webmvc/src/test/java/org/springframework/web/servlet/config/MvcNamespaceTests.java

@ -84,6 +84,7 @@ import org.springframework.web.context.request.async.CallableProcessingIntercept @@ -84,6 +84,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.GenericWebApplicationContext;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.method.support.CompositeUriComponentsContributor;
import org.springframework.web.method.support.InvocableHandlerMethod;
@ -91,6 +92,7 @@ import org.springframework.web.servlet.HandlerExecutionChain; @@ -91,6 +92,7 @@ import org.springframework.web.servlet.HandlerExecutionChain;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.ViewResolver;
import org.springframework.web.servlet.handler.AbstractHandlerMapping;
import org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping;
import org.springframework.web.servlet.handler.ConversionServiceExposingInterceptor;
import org.springframework.web.servlet.handler.MappedInterceptor;
@ -187,7 +189,7 @@ public class MvcNamespaceTests { @@ -187,7 +189,7 @@ public class MvcNamespaceTests {
@Test
public void testDefaultConfig() throws Exception {
loadBeanDefinitions("mvc-config.xml", 13);
loadBeanDefinitions("mvc-config.xml", 14);
RequestMappingHandlerMapping mapping = appContext.getBean(RequestMappingHandlerMapping.class);
assertNotNull(mapping);
@ -250,7 +252,7 @@ public class MvcNamespaceTests { @@ -250,7 +252,7 @@ public class MvcNamespaceTests {
@Test(expected = TypeMismatchException.class)
public void testCustomConversionService() throws Exception {
loadBeanDefinitions("mvc-config-custom-conversion-service.xml", 13);
loadBeanDefinitions("mvc-config-custom-conversion-service.xml", 14);
RequestMappingHandlerMapping mapping = appContext.getBean(RequestMappingHandlerMapping.class);
assertNotNull(mapping);
@ -285,7 +287,7 @@ public class MvcNamespaceTests { @@ -285,7 +287,7 @@ public class MvcNamespaceTests {
}
private void doTestCustomValidator(String xml) throws Exception {
loadBeanDefinitions(xml, 13);
loadBeanDefinitions(xml, 14);
RequestMappingHandlerMapping mapping = appContext.getBean(RequestMappingHandlerMapping.class);
assertNotNull(mapping);
@ -307,7 +309,7 @@ public class MvcNamespaceTests { @@ -307,7 +309,7 @@ public class MvcNamespaceTests {
@Test
public void testInterceptors() throws Exception {
loadBeanDefinitions("mvc-config-interceptors.xml", 20);
loadBeanDefinitions("mvc-config-interceptors.xml", 21);
RequestMappingHandlerMapping mapping = appContext.getBean(RequestMappingHandlerMapping.class);
assertNotNull(mapping);
@ -343,7 +345,7 @@ public class MvcNamespaceTests { @@ -343,7 +345,7 @@ public class MvcNamespaceTests {
@Test
public void testResources() throws Exception {
loadBeanDefinitions("mvc-config-resources.xml", 9);
loadBeanDefinitions("mvc-config-resources.xml", 10);
HttpRequestHandlerAdapter adapter = appContext.getBean(HttpRequestHandlerAdapter.class);
assertNotNull(adapter);
@ -383,7 +385,7 @@ public class MvcNamespaceTests { @@ -383,7 +385,7 @@ public class MvcNamespaceTests {
@Test
public void testResourcesWithOptionalAttributes() throws Exception {
loadBeanDefinitions("mvc-config-resources-optional-attrs.xml", 9);
loadBeanDefinitions("mvc-config-resources-optional-attrs.xml", 10);
SimpleUrlHandlerMapping mapping = appContext.getBean(SimpleUrlHandlerMapping.class);
assertNotNull(mapping);
@ -400,7 +402,7 @@ public class MvcNamespaceTests { @@ -400,7 +402,7 @@ public class MvcNamespaceTests {
@Test
public void testResourcesWithResolversTransformers() throws Exception {
loadBeanDefinitions("mvc-config-resources-chain.xml", 10);
loadBeanDefinitions("mvc-config-resources-chain.xml", 11);
SimpleUrlHandlerMapping mapping = appContext.getBean(SimpleUrlHandlerMapping.class);
assertNotNull(mapping);
@ -439,7 +441,7 @@ public class MvcNamespaceTests { @@ -439,7 +441,7 @@ public class MvcNamespaceTests {
@Test
public void testResourcesWithResolversTransformersCustom() throws Exception {
loadBeanDefinitions("mvc-config-resources-chain-no-auto.xml", 11);
loadBeanDefinitions("mvc-config-resources-chain-no-auto.xml", 12);
SimpleUrlHandlerMapping mapping = appContext.getBean(SimpleUrlHandlerMapping.class);
assertNotNull(mapping);
@ -472,7 +474,7 @@ public class MvcNamespaceTests { @@ -472,7 +474,7 @@ public class MvcNamespaceTests {
@Test
public void testDefaultServletHandler() throws Exception {
loadBeanDefinitions("mvc-config-default-servlet.xml", 5);
loadBeanDefinitions("mvc-config-default-servlet.xml", 6);
HttpRequestHandlerAdapter adapter = appContext.getBean(HttpRequestHandlerAdapter.class);
assertNotNull(adapter);
@ -498,7 +500,7 @@ public class MvcNamespaceTests { @@ -498,7 +500,7 @@ public class MvcNamespaceTests {
@Test
public void testDefaultServletHandlerWithOptionalAttributes() throws Exception {
loadBeanDefinitions("mvc-config-default-servlet-optional-attrs.xml", 5);
loadBeanDefinitions("mvc-config-default-servlet-optional-attrs.xml", 6);
HttpRequestHandlerAdapter adapter = appContext.getBean(HttpRequestHandlerAdapter.class);
assertNotNull(adapter);
@ -524,7 +526,7 @@ public class MvcNamespaceTests { @@ -524,7 +526,7 @@ public class MvcNamespaceTests {
@Test
public void testBeanDecoration() throws Exception {
loadBeanDefinitions("mvc-config-bean-decoration.xml", 15);
loadBeanDefinitions("mvc-config-bean-decoration.xml", 16);
RequestMappingHandlerMapping mapping = appContext.getBean(RequestMappingHandlerMapping.class);
assertNotNull(mapping);
@ -545,7 +547,7 @@ public class MvcNamespaceTests { @@ -545,7 +547,7 @@ public class MvcNamespaceTests {
@Test
public void testViewControllers() throws Exception {
loadBeanDefinitions("mvc-config-view-controllers.xml", 18);
loadBeanDefinitions("mvc-config-view-controllers.xml", 19);
RequestMappingHandlerMapping mapping = appContext.getBean(RequestMappingHandlerMapping.class);
assertNotNull(mapping);
@ -626,7 +628,7 @@ public class MvcNamespaceTests { @@ -626,7 +628,7 @@ public class MvcNamespaceTests {
/** WebSphere gives trailing servlet path slashes by default!! */
@Test
public void testViewControllersOnWebSphere() throws Exception {
loadBeanDefinitions("mvc-config-view-controllers.xml", 18);
loadBeanDefinitions("mvc-config-view-controllers.xml", 19);
SimpleUrlHandlerMapping mapping2 = appContext.getBean(SimpleUrlHandlerMapping.class);
SimpleControllerHandlerAdapter adapter = appContext.getBean(SimpleControllerHandlerAdapter.class);
@ -670,7 +672,7 @@ public class MvcNamespaceTests { @@ -670,7 +672,7 @@ public class MvcNamespaceTests {
@Test
public void testViewControllersDefaultConfig() {
loadBeanDefinitions("mvc-config-view-controllers-minimal.xml", 6);
loadBeanDefinitions("mvc-config-view-controllers-minimal.xml", 7);
SimpleUrlHandlerMapping hm = this.appContext.getBean(SimpleUrlHandlerMapping.class);
assertNotNull(hm);
@ -693,7 +695,7 @@ public class MvcNamespaceTests { @@ -693,7 +695,7 @@ public class MvcNamespaceTests {
@Test
public void testContentNegotiationManager() throws Exception {
loadBeanDefinitions("mvc-config-content-negotiation-manager.xml", 13);
loadBeanDefinitions("mvc-config-content-negotiation-manager.xml", 14);
RequestMappingHandlerMapping mapping = appContext.getBean(RequestMappingHandlerMapping.class);
ContentNegotiationManager manager = mapping.getContentNegotiationManager();
@ -705,7 +707,7 @@ public class MvcNamespaceTests { @@ -705,7 +707,7 @@ public class MvcNamespaceTests {
@Test
public void testAsyncSupportOptions() throws Exception {
loadBeanDefinitions("mvc-config-async-support.xml", 14);
loadBeanDefinitions("mvc-config-async-support.xml", 15);
RequestMappingHandlerAdapter adapter = appContext.getBean(RequestMappingHandlerAdapter.class);
assertNotNull(adapter);
@ -845,7 +847,7 @@ public class MvcNamespaceTests { @@ -845,7 +847,7 @@ public class MvcNamespaceTests {
@Test
public void testPathMatchingHandlerMappings() throws Exception {
loadBeanDefinitions("mvc-config-path-matching-mappings.xml", 22);
loadBeanDefinitions("mvc-config-path-matching-mappings.xml", 23);
RequestMappingHandlerMapping requestMapping = appContext.getBean(RequestMappingHandlerMapping.class);
assertNotNull(requestMapping);
@ -864,6 +866,59 @@ public class MvcNamespaceTests { @@ -864,6 +866,59 @@ public class MvcNamespaceTests {
}
}
@Test
public void testCorsMinimal() throws Exception {
loadBeanDefinitions("mvc-config-cors-minimal.xml", 14);
String[] beanNames = appContext.getBeanNamesForType(AbstractHandlerMapping.class);
assertEquals(2, beanNames.length);
for (String beanName : beanNames) {
AbstractHandlerMapping handlerMapping = (AbstractHandlerMapping)appContext.getBean(beanName);
assertNotNull(handlerMapping);
Map<String, CorsConfiguration> configs = handlerMapping.getCorsConfiguration();
assertNotNull(configs);
assertEquals(1, configs.size());
CorsConfiguration config = configs.get("/**");
assertNotNull(config);
assertArrayEquals(new String[]{"*"}, config.getAllowedOrigins().toArray());
assertArrayEquals(new String[]{"GET", "HEAD", "POST"}, config.getAllowedMethods().toArray());
assertArrayEquals(new String[]{"*"}, config.getAllowedHeaders().toArray());
assertNull(config.getExposedHeaders());
assertTrue(config.getAllowCredentials());
assertEquals(new Long(1600), config.getMaxAge());
}
}
@Test
public void testCors() throws Exception {
loadBeanDefinitions("mvc-config-cors.xml", 14);
String[] beanNames = appContext.getBeanNamesForType(AbstractHandlerMapping.class);
assertEquals(2, beanNames.length);
for (String beanName : beanNames) {
AbstractHandlerMapping handlerMapping = (AbstractHandlerMapping)appContext.getBean(beanName);
assertNotNull(handlerMapping);
Map<String, CorsConfiguration> configs = handlerMapping.getCorsConfiguration();
assertNotNull(configs);
assertEquals(2, configs.size());
CorsConfiguration config = configs.get("/api/**");
assertNotNull(config);
assertArrayEquals(new String[]{"http://domain1.com", "http://domain2.com"}, config.getAllowedOrigins().toArray());
assertArrayEquals(new String[]{"GET", "PUT"}, config.getAllowedMethods().toArray());
assertArrayEquals(new String[]{"header1", "header2", "header3"}, config.getAllowedHeaders().toArray());
assertArrayEquals(new String[]{"header1", "header2"}, config.getExposedHeaders().toArray());
assertFalse(config.getAllowCredentials());
assertEquals(new Long(123), config.getMaxAge());
config = configs.get("/resources/**");
assertArrayEquals(new String[]{"http://domain1.com"}, config.getAllowedOrigins().toArray());
assertArrayEquals(new String[]{"GET", "HEAD", "POST"}, config.getAllowedMethods().toArray());
assertArrayEquals(new String[]{"*"}, config.getAllowedHeaders().toArray());
assertNull(config.getExposedHeaders());
assertTrue(config.getAllowCredentials());
assertEquals(new Long(1600), config.getMaxAge());
}
}
private void loadBeanDefinitions(String fileName, int expectedBeanCount) {
XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(appContext);

13
spring-webmvc/src/test/resources/org/springframework/web/servlet/config/mvc-config-cors-minimal.xml

@ -0,0 +1,13 @@ @@ -0,0 +1,13 @@
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd">
<!-- <mvc:cors /> element before <mvc:annotation-driven /> one -->
<mvc:cors />
<mvc:annotation-driven />
</beans>

20
spring-webmvc/src/test/resources/org/springframework/web/servlet/config/mvc-config-cors.xml

@ -0,0 +1,20 @@ @@ -0,0 +1,20 @@
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd">
<mvc:annotation-driven />
<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>
</beans>
Loading…
Cancel
Save