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;
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
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 {
|
||||
|
||||
@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 {
|
||||
|
||||
@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 {
|
||||
}
|
||||
|
||||
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 {
|
||||
|
||||
@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 {
|
||||
|
||||
@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 {
|
||||
|
||||
@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 {
|
||||
|
||||
@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 {
|
||||
|
||||
@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 {
|
||||
|
||||
@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 {
|
||||
|
||||
@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 {
|
||||
|
||||
@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 {
|
||||
|
||||
@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 {
|
||||
/** 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 {
|
||||
|
||||
@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 {
|
||||
|
||||
@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 {
|
||||
|
||||
@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 {
|
||||
|
||||
@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 {
|
||||
}
|
||||
}
|
||||
|
||||
@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);
|
||||
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
Reference in New Issue
Block a user