Support custom CorsConfigurationSource in AbstractHandlerMapping
This commit allows to specify a custom CorsConfigurationSource in AbstractHandlerMapping (both Servlet and Reactive variants). AbstractHandlerMapping#getCorsConfigurations method is now deprecated. Issue: SPR-17067
This commit is contained in:
parent
b325c74216
commit
7e9b7102b7
|
@ -53,7 +53,7 @@ public abstract class AbstractHandlerMapping extends ApplicationObjectSupport
|
|||
|
||||
private final PathPatternParser patternParser;
|
||||
|
||||
private final UrlBasedCorsConfigurationSource globalCorsConfigSource;
|
||||
private CorsConfigurationSource corsConfigurationSource;
|
||||
|
||||
private CorsProcessor corsProcessor = new DefaultCorsProcessor();
|
||||
|
||||
|
@ -65,7 +65,7 @@ public abstract class AbstractHandlerMapping extends ApplicationObjectSupport
|
|||
|
||||
public AbstractHandlerMapping() {
|
||||
this.patternParser = new PathPatternParser();
|
||||
this.globalCorsConfigSource = new UrlBasedCorsConfigurationSource(this.patternParser);
|
||||
this.corsConfigurationSource = new UrlBasedCorsConfigurationSource(this.patternParser);
|
||||
}
|
||||
|
||||
|
||||
|
@ -107,12 +107,25 @@ public abstract class AbstractHandlerMapping extends ApplicationObjectSupport
|
|||
}
|
||||
|
||||
/**
|
||||
* Set "global" CORS configuration based on URL patterns. By default the
|
||||
* first matching URL pattern is combined with handler-level CORS
|
||||
* configuration if any.
|
||||
* Set the "global" CORS configurations based on URL patterns. By default the
|
||||
* first matching URL pattern is combined with handler-level CORS configuration if any.
|
||||
* @see #setCorsConfigurationSource(CorsConfigurationSource)
|
||||
*/
|
||||
public void setCorsConfigurations(Map<String, CorsConfiguration> corsConfigurations) {
|
||||
this.globalCorsConfigSource.setCorsConfigurations(corsConfigurations);
|
||||
Assert.notNull(corsConfigurations, "corsConfigurations must not be null");
|
||||
this.corsConfigurationSource = new UrlBasedCorsConfigurationSource(this.patternParser);
|
||||
((UrlBasedCorsConfigurationSource) this.corsConfigurationSource).setCorsConfigurations(corsConfigurations);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the "global" CORS configuration source. By default the first matching URL
|
||||
* pattern is combined with the CORS configuration for the handler, if any.
|
||||
* @since 5.1
|
||||
* @see #setCorsConfigurations(Map)
|
||||
*/
|
||||
public void setCorsConfigurationSource(CorsConfigurationSource corsConfigurationSource) {
|
||||
Assert.notNull(corsConfigurationSource, "corsConfigurationSource must not be null");
|
||||
this.corsConfigurationSource = corsConfigurationSource;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -163,7 +176,7 @@ public abstract class AbstractHandlerMapping extends ApplicationObjectSupport
|
|||
logger.debug(exchange.getLogPrefix() + "Mapped to " + handler);
|
||||
}
|
||||
if (CorsUtils.isCorsRequest(exchange.getRequest())) {
|
||||
CorsConfiguration configA = this.globalCorsConfigSource.getCorsConfiguration(exchange);
|
||||
CorsConfiguration configA = this.corsConfigurationSource.getCorsConfiguration(exchange);
|
||||
CorsConfiguration configB = getCorsConfiguration(handler, exchange);
|
||||
CorsConfiguration config = (configA != null ? configA.combine(configB) : configB);
|
||||
if (!getCorsProcessor().process(config, exchange) ||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2002-2016 the original author or authors.
|
||||
* Copyright 2002-2018 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.
|
||||
|
@ -130,6 +130,38 @@ public class CorsUrlHandlerMappingTests {
|
|||
assertEquals("*", exchange.getResponse().getHeaders().getFirst(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void actualRequestWithCorsConfigurationSource() throws Exception {
|
||||
this.handlerMapping.setCorsConfigurationSource(new CustomCorsConfigurationSource());
|
||||
|
||||
String origin = "http://domain2.com";
|
||||
ServerWebExchange exchange = createExchange(HttpMethod.GET, "/welcome.html", origin);
|
||||
Object actual = this.handlerMapping.getHandler(exchange).block();
|
||||
|
||||
assertNotNull(actual);
|
||||
assertSame(this.welcomeController, actual);
|
||||
assertEquals("http://domain2.com", exchange.getResponse().getHeaders()
|
||||
.getFirst(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN));
|
||||
assertEquals("true", exchange.getResponse().getHeaders()
|
||||
.getFirst(HttpHeaders.ACCESS_CONTROL_ALLOW_CREDENTIALS));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void preFlightRequestWithCorsConfigurationSource() throws Exception {
|
||||
this.handlerMapping.setCorsConfigurationSource(new CustomCorsConfigurationSource());
|
||||
|
||||
String origin = "http://domain2.com";
|
||||
ServerWebExchange exchange = createExchange(HttpMethod.OPTIONS, "/welcome.html", origin);
|
||||
Object actual = this.handlerMapping.getHandler(exchange).block();
|
||||
|
||||
assertNotNull(actual);
|
||||
assertNotSame(this.welcomeController, actual);
|
||||
assertEquals("http://domain2.com", exchange.getResponse().getHeaders()
|
||||
.getFirst(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN));
|
||||
assertEquals("true", exchange.getResponse().getHeaders()
|
||||
.getFirst(HttpHeaders.ACCESS_CONTROL_ALLOW_CREDENTIALS));
|
||||
}
|
||||
|
||||
|
||||
private ServerWebExchange createExchange(HttpMethod method, String path, String origin) {
|
||||
|
||||
|
@ -150,4 +182,15 @@ public class CorsUrlHandlerMappingTests {
|
|||
}
|
||||
}
|
||||
|
||||
public class CustomCorsConfigurationSource implements CorsConfigurationSource {
|
||||
|
||||
@Override
|
||||
public CorsConfiguration getCorsConfiguration(ServerWebExchange exchange) {
|
||||
CorsConfiguration config = new CorsConfiguration();
|
||||
config.addAllowedOrigin("*");
|
||||
config.setAllowCredentials(true);
|
||||
return config;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -81,7 +81,7 @@ public abstract class AbstractHandlerMapping extends WebApplicationObjectSupport
|
|||
|
||||
private final List<HandlerInterceptor> adaptedInterceptors = new ArrayList<>();
|
||||
|
||||
private final UrlBasedCorsConfigurationSource globalCorsConfigSource = new UrlBasedCorsConfigurationSource();
|
||||
private CorsConfigurationSource corsConfigurationSource = new UrlBasedCorsConfigurationSource();
|
||||
|
||||
private CorsProcessor corsProcessor = new DefaultCorsProcessor();
|
||||
|
||||
|
@ -115,7 +115,9 @@ public abstract class AbstractHandlerMapping extends WebApplicationObjectSupport
|
|||
*/
|
||||
public void setAlwaysUseFullPath(boolean alwaysUseFullPath) {
|
||||
this.urlPathHelper.setAlwaysUseFullPath(alwaysUseFullPath);
|
||||
this.globalCorsConfigSource.setAlwaysUseFullPath(alwaysUseFullPath);
|
||||
if (this.corsConfigurationSource instanceof UrlBasedCorsConfigurationSource) {
|
||||
((UrlBasedCorsConfigurationSource)this.corsConfigurationSource).setAlwaysUseFullPath(alwaysUseFullPath);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -124,7 +126,9 @@ public abstract class AbstractHandlerMapping extends WebApplicationObjectSupport
|
|||
*/
|
||||
public void setUrlDecode(boolean urlDecode) {
|
||||
this.urlPathHelper.setUrlDecode(urlDecode);
|
||||
this.globalCorsConfigSource.setUrlDecode(urlDecode);
|
||||
if (this.corsConfigurationSource instanceof UrlBasedCorsConfigurationSource) {
|
||||
((UrlBasedCorsConfigurationSource)this.corsConfigurationSource).setUrlDecode(urlDecode);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -133,7 +137,9 @@ public abstract class AbstractHandlerMapping extends WebApplicationObjectSupport
|
|||
*/
|
||||
public void setRemoveSemicolonContent(boolean removeSemicolonContent) {
|
||||
this.urlPathHelper.setRemoveSemicolonContent(removeSemicolonContent);
|
||||
this.globalCorsConfigSource.setRemoveSemicolonContent(removeSemicolonContent);
|
||||
if (this.corsConfigurationSource instanceof UrlBasedCorsConfigurationSource) {
|
||||
((UrlBasedCorsConfigurationSource)this.corsConfigurationSource).setRemoveSemicolonContent(removeSemicolonContent);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -145,7 +151,9 @@ public abstract class AbstractHandlerMapping extends WebApplicationObjectSupport
|
|||
public void setUrlPathHelper(UrlPathHelper urlPathHelper) {
|
||||
Assert.notNull(urlPathHelper, "UrlPathHelper must not be null");
|
||||
this.urlPathHelper = urlPathHelper;
|
||||
this.globalCorsConfigSource.setUrlPathHelper(urlPathHelper);
|
||||
if (this.corsConfigurationSource instanceof UrlBasedCorsConfigurationSource) {
|
||||
((UrlBasedCorsConfigurationSource)this.corsConfigurationSource).setUrlPathHelper(urlPathHelper);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -163,7 +171,9 @@ public abstract class AbstractHandlerMapping extends WebApplicationObjectSupport
|
|||
public void setPathMatcher(PathMatcher pathMatcher) {
|
||||
Assert.notNull(pathMatcher, "PathMatcher must not be null");
|
||||
this.pathMatcher = pathMatcher;
|
||||
this.globalCorsConfigSource.setPathMatcher(pathMatcher);
|
||||
if (this.corsConfigurationSource instanceof UrlBasedCorsConfigurationSource) {
|
||||
((UrlBasedCorsConfigurationSource)this.corsConfigurationSource).setPathMatcher(pathMatcher);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -189,20 +199,45 @@ public abstract class AbstractHandlerMapping extends WebApplicationObjectSupport
|
|||
}
|
||||
|
||||
/**
|
||||
* Set "global" CORS configuration based on URL patterns. By default the first
|
||||
* matching URL pattern is combined with the CORS configuration for the
|
||||
* handler, if any.
|
||||
* Set the "global" CORS configurations based on URL patterns. By default the first
|
||||
* matching URL pattern is combined with the CORS configuration for the handler, if any.
|
||||
* @since 4.2
|
||||
* @see #setCorsConfigurationSource(CorsConfigurationSource)
|
||||
*/
|
||||
public void setCorsConfigurations(Map<String, CorsConfiguration> corsConfigurations) {
|
||||
this.globalCorsConfigSource.setCorsConfigurations(corsConfigurations);
|
||||
Assert.notNull(corsConfigurations, "corsConfigurations must not be null");
|
||||
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
|
||||
source.setCorsConfigurations(corsConfigurations);
|
||||
source.setPathMatcher(this.pathMatcher);
|
||||
source.setUrlPathHelper(this.urlPathHelper);
|
||||
this.corsConfigurationSource = source;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the "global" CORS configuration.
|
||||
* Set the "global" CORS configuration source. By default the first matching URL
|
||||
* pattern is combined with the CORS configuration for the handler, if any.
|
||||
* @since 5.1
|
||||
* @see #setCorsConfigurations(Map)
|
||||
*/
|
||||
public void setCorsConfigurationSource(CorsConfigurationSource corsConfigurationSource) {
|
||||
Assert.notNull(corsConfigurationSource, "corsConfigurationSource must not be null");
|
||||
this.corsConfigurationSource = corsConfigurationSource;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the "global" CORS configurations.
|
||||
* @deprecated as of 5.1 since it is now possible to set a {@link CorsConfigurationSource} which is not a
|
||||
* {@link UrlBasedCorsConfigurationSource}. Expected to be removed in 5.2.
|
||||
*/
|
||||
@Deprecated
|
||||
public Map<String, CorsConfiguration> getCorsConfigurations() {
|
||||
return this.globalCorsConfigSource.getCorsConfigurations();
|
||||
if (this.corsConfigurationSource instanceof UrlBasedCorsConfigurationSource) {
|
||||
return ((UrlBasedCorsConfigurationSource)this.corsConfigurationSource).getCorsConfigurations();
|
||||
}
|
||||
else {
|
||||
throw new IllegalStateException("No CORS configurations available when the source " +
|
||||
"is not an UrlBasedCorsConfigurationSource");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -286,7 +321,8 @@ public abstract class AbstractHandlerMapping extends WebApplicationObjectSupport
|
|||
|
||||
/**
|
||||
* Initialize the specified interceptors, checking for {@link MappedInterceptor MappedInterceptors} and
|
||||
* adapting {@link HandlerInterceptor}s and {@link WebRequestInterceptor HandlerInterceptor}s and {@link WebRequestInterceptors} if necessary.
|
||||
* adapting {@link HandlerInterceptor}s and {@link WebRequestInterceptor HandlerInterceptor}s and
|
||||
* {@link WebRequestInterceptor}s if necessary.
|
||||
* @see #setInterceptors
|
||||
* @see #adaptInterceptor
|
||||
*/
|
||||
|
@ -385,7 +421,7 @@ public abstract class AbstractHandlerMapping extends WebApplicationObjectSupport
|
|||
}
|
||||
|
||||
if (CorsUtils.isCorsRequest(request)) {
|
||||
CorsConfiguration globalConfig = this.globalCorsConfigSource.getCorsConfiguration(request);
|
||||
CorsConfiguration globalConfig = this.corsConfigurationSource.getCorsConfiguration(request);
|
||||
CorsConfiguration handlerConfig = getCorsConfiguration(handler, request);
|
||||
CorsConfiguration config = (globalConfig != null ? globalConfig.combine(handlerConfig) : handlerConfig);
|
||||
executionChain = getCorsHandlerExecutionChain(request, executionChain, config);
|
||||
|
@ -402,7 +438,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 #getCorsConfigurations},
|
||||
* the CORS configuration to be obtained via {@link #getCorsConfiguration(Object, HttpServletRequest)},
|
||||
* <p>Note: This method may also return a pre-built {@link HandlerExecutionChain},
|
||||
* combining a handler object with dynamically determined interceptors.
|
||||
* Statically specified interceptors will get merged into such an existing chain.
|
||||
|
|
|
@ -85,6 +85,7 @@ import org.springframework.web.context.request.async.CallableProcessingIntercept
|
|||
import org.springframework.web.context.request.async.DeferredResultProcessingInterceptor;
|
||||
import org.springframework.web.context.support.XmlWebApplicationContext;
|
||||
import org.springframework.web.cors.CorsConfiguration;
|
||||
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
|
||||
import org.springframework.web.method.HandlerMethod;
|
||||
import org.springframework.web.method.support.CompositeUriComponentsContributor;
|
||||
import org.springframework.web.method.support.InvocableHandlerMethod;
|
||||
|
@ -887,7 +888,9 @@ public class MvcNamespaceTests {
|
|||
for (String beanName : beanNames) {
|
||||
AbstractHandlerMapping handlerMapping = (AbstractHandlerMapping)appContext.getBean(beanName);
|
||||
assertNotNull(handlerMapping);
|
||||
Map<String, CorsConfiguration> configs = handlerMapping.getCorsConfigurations();
|
||||
DirectFieldAccessor accessor = new DirectFieldAccessor(handlerMapping);
|
||||
Map<String, CorsConfiguration> configs = ((UrlBasedCorsConfigurationSource)accessor
|
||||
.getPropertyValue("corsConfigurationSource")).getCorsConfigurations();
|
||||
assertNotNull(configs);
|
||||
assertEquals(1, configs.size());
|
||||
CorsConfiguration config = configs.get("/**");
|
||||
|
@ -910,7 +913,9 @@ public class MvcNamespaceTests {
|
|||
for (String beanName : beanNames) {
|
||||
AbstractHandlerMapping handlerMapping = (AbstractHandlerMapping)appContext.getBean(beanName);
|
||||
assertNotNull(handlerMapping);
|
||||
Map<String, CorsConfiguration> configs = handlerMapping.getCorsConfigurations();
|
||||
DirectFieldAccessor accessor = new DirectFieldAccessor(handlerMapping);
|
||||
Map<String, CorsConfiguration> configs = ((UrlBasedCorsConfigurationSource)accessor
|
||||
.getPropertyValue("corsConfigurationSource")).getCorsConfigurations();
|
||||
assertNotNull(configs);
|
||||
assertEquals(2, configs.size());
|
||||
CorsConfiguration config = configs.get("/api/**");
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2002-2015 the original author or authors.
|
||||
* Copyright 2002-2018 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.
|
||||
|
@ -149,6 +149,39 @@ public class CorsAbstractHandlerMappingTests {
|
|||
assertArrayEquals(config.getAllowedOrigins().toArray(), new String[]{"*"});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void actualRequestWithCorsConfigurationSource() throws Exception {
|
||||
this.handlerMapping.setCorsConfigurationSource(new CustomCorsConfigurationSource());
|
||||
this.request.setMethod(RequestMethod.GET.name());
|
||||
this.request.setRequestURI("/foo");
|
||||
this.request.addHeader(HttpHeaders.ORIGIN, "http://domain2.com");
|
||||
this.request.addHeader(HttpHeaders.ACCESS_CONTROL_REQUEST_METHOD, "GET");
|
||||
HandlerExecutionChain chain = handlerMapping.getHandler(this.request);
|
||||
assertNotNull(chain);
|
||||
assertTrue(chain.getHandler() instanceof SimpleHandler);
|
||||
CorsConfiguration config = getCorsConfiguration(chain, false);
|
||||
assertNotNull(config);
|
||||
assertArrayEquals(new String[]{"*"}, config.getAllowedOrigins().toArray());
|
||||
assertEquals(true, config.getAllowCredentials());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void preflightRequestWithCorsConfigurationSource() throws Exception {
|
||||
this.handlerMapping.setCorsConfigurationSource(new CustomCorsConfigurationSource());
|
||||
this.request.setMethod(RequestMethod.OPTIONS.name());
|
||||
this.request.setRequestURI("/foo");
|
||||
this.request.addHeader(HttpHeaders.ORIGIN, "http://domain2.com");
|
||||
this.request.addHeader(HttpHeaders.ACCESS_CONTROL_REQUEST_METHOD, "GET");
|
||||
HandlerExecutionChain chain = handlerMapping.getHandler(this.request);
|
||||
assertNotNull(chain);
|
||||
assertNotNull(chain.getHandler());
|
||||
assertTrue(chain.getHandler().getClass().getSimpleName().equals("PreFlightHandler"));
|
||||
CorsConfiguration config = getCorsConfiguration(chain, true);
|
||||
assertNotNull(config);
|
||||
assertArrayEquals(new String[]{"*"}, config.getAllowedOrigins().toArray());
|
||||
assertEquals(true, config.getAllowCredentials());
|
||||
}
|
||||
|
||||
|
||||
private CorsConfiguration getCorsConfiguration(HandlerExecutionChain chain, boolean isPreFlightRequest) {
|
||||
if (isPreFlightRequest) {
|
||||
|
@ -208,4 +241,15 @@ public class CorsAbstractHandlerMappingTests {
|
|||
|
||||
}
|
||||
|
||||
public class CustomCorsConfigurationSource implements CorsConfigurationSource {
|
||||
|
||||
@Override
|
||||
public CorsConfiguration getCorsConfiguration(HttpServletRequest request) {
|
||||
CorsConfiguration config = new CorsConfiguration();
|
||||
config.addAllowedOrigin("*");
|
||||
config.setAllowCredentials(true);
|
||||
return config;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue