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