Improve CORS handling

This commit improves CORS support by:
 - Using CORS processing only for CORS-enabled endpoints
 - Skipping CORS processing for same-origin requests
 - Adding Vary headers for non-CORS requests

It introduces an AbstractHandlerMapping#hasCorsConfigurationSource
method in order to be able to check CORS endpoints efficiently.

Closes gh-22273
Closes gh-22496
This commit is contained in:
Sebastien Deleuze
2019-04-01 14:36:38 +02:00
parent 8714710170
commit d27b5d0ab6
20 changed files with 278 additions and 123 deletions

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2018 the original author or authors.
* Copyright 2002-2019 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.
@@ -81,7 +81,8 @@ public abstract class AbstractHandlerMapping extends WebApplicationObjectSupport
private final List<HandlerInterceptor> adaptedInterceptors = new ArrayList<>();
private CorsConfigurationSource corsConfigurationSource = new UrlBasedCorsConfigurationSource();
@Nullable
private CorsConfigurationSource corsConfigurationSource;
private CorsProcessor corsProcessor = new DefaultCorsProcessor();
@@ -206,11 +207,16 @@ public abstract class AbstractHandlerMapping extends WebApplicationObjectSupport
*/
public void setCorsConfigurations(Map<String, CorsConfiguration> 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;
if (!corsConfigurations.isEmpty()) {
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.setCorsConfigurations(corsConfigurations);
source.setPathMatcher(this.pathMatcher);
source.setUrlPathHelper(this.urlPathHelper);
this.corsConfigurationSource = source;
}
else {
this.corsConfigurationSource = null;
}
}
/**
@@ -420,10 +426,10 @@ public abstract class AbstractHandlerMapping extends WebApplicationObjectSupport
logger.debug("Mapped to " + executionChain.getHandler());
}
if (CorsUtils.isCorsRequest(request)) {
CorsConfiguration globalConfig = this.corsConfigurationSource.getCorsConfiguration(request);
if (hasCorsConfigurationSource(handler)) {
CorsConfiguration config = (this.corsConfigurationSource != null ? this.corsConfigurationSource.getCorsConfiguration(request) : null);
CorsConfiguration handlerConfig = getCorsConfiguration(handler, request);
CorsConfiguration config = (globalConfig != null ? globalConfig.combine(handlerConfig) : handlerConfig);
config = (config != null ? config.combine(handlerConfig) : handlerConfig);
executionChain = getCorsHandlerExecutionChain(request, executionChain, config);
}
@@ -488,6 +494,14 @@ public abstract class AbstractHandlerMapping extends WebApplicationObjectSupport
return chain;
}
/**
* Return {@code true} if there is a {@link CorsConfigurationSource} for this handler.
* @since 5.2
*/
protected boolean hasCorsConfigurationSource(Object handler) {
return handler instanceof CorsConfigurationSource || this.corsConfigurationSource != null;
}
/**
* Retrieve the CORS configuration for the given handler.
* @param handler the handler to check (never {@code null}).

View File

@@ -448,6 +448,13 @@ public abstract class AbstractHandlerMethodMapping<T> extends AbstractHandlerMap
return null;
}
@Override
protected boolean hasCorsConfigurationSource(Object handler) {
return super.hasCorsConfigurationSource(handler) ||
(handler instanceof HandlerMethod && this.mappingRegistry.getCorsConfiguration((HandlerMethod) handler) != null) ||
handler.equals(PREFLIGHT_AMBIGUOUS_MATCH);
}
@Override
protected CorsConfiguration getCorsConfiguration(Object handler, HttpServletRequest request) {
CorsConfiguration corsConfig = super.getCorsConfiguration(handler, request);
@@ -555,6 +562,7 @@ public abstract class AbstractHandlerMethodMapping<T> extends AbstractHandlerMap
/**
* Return CORS configuration. Thread-safe for concurrent use.
*/
@Nullable
public CorsConfiguration getCorsConfiguration(HandlerMethod handlerMethod) {
HandlerMethod original = handlerMethod.getResolvedFromHandlerMethod();
return this.corsLookup.get(original != null ? original : handlerMethod);

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2018 the original author or authors.
* Copyright 2002-2019 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.
@@ -138,7 +138,7 @@ public class WebMvcConfigurationSupportExtensionTests {
HandlerExecutionChain chain = rmHandlerMapping.getHandler(new MockHttpServletRequest("GET", "/"));
assertNotNull(chain);
assertNotNull(chain.getInterceptors());
assertEquals(3, chain.getInterceptors().length);
assertEquals(4, chain.getInterceptors().length);
assertEquals(LocaleChangeInterceptor.class, chain.getInterceptors()[0].getClass());
assertEquals(ConversionServiceExposingInterceptor.class, chain.getInterceptors()[1].getClass());
assertEquals(ResourceUrlProviderExposingInterceptor.class, chain.getInterceptors()[2].getClass());
@@ -177,7 +177,7 @@ public class WebMvcConfigurationSupportExtensionTests {
chain = handlerMapping.getHandler(new MockHttpServletRequest("GET", "/resources/foo.gif"));
assertNotNull(chain);
assertNotNull(chain.getHandler());
assertEquals(Arrays.toString(chain.getInterceptors()), 4, chain.getInterceptors().length);
assertEquals(Arrays.toString(chain.getInterceptors()), 5, chain.getInterceptors().length);
// PathExposingHandlerInterceptor at chain.getInterceptors()[0]
assertEquals(LocaleChangeInterceptor.class, chain.getInterceptors()[1].getClass());
assertEquals(ConversionServiceExposingInterceptor.class, chain.getInterceptors()[2].getClass());

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2018 the original author or authors.
* Copyright 2002-2019 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.
@@ -81,8 +81,7 @@ public class CorsAbstractHandlerMappingTests {
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"));
assertTrue(chain.getHandler() instanceof SimpleHandler);
}
@Test