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.
@@ -23,6 +23,7 @@ import reactor.core.publisher.Mono;
import org.springframework.beans.factory.BeanNameAware;
import org.springframework.context.support.ApplicationObjectSupport;
import org.springframework.core.Ordered;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.web.cors.CorsConfiguration;
@@ -53,6 +54,7 @@ public abstract class AbstractHandlerMapping extends ApplicationObjectSupport
private final PathPatternParser patternParser;
@Nullable
private CorsConfigurationSource corsConfigurationSource;
private CorsProcessor corsProcessor = new DefaultCorsProcessor();
@@ -65,7 +67,6 @@ public abstract class AbstractHandlerMapping extends ApplicationObjectSupport
public AbstractHandlerMapping() {
this.patternParser = new PathPatternParser();
this.corsConfigurationSource = new UrlBasedCorsConfigurationSource(this.patternParser);
}
@@ -113,8 +114,14 @@ public abstract class AbstractHandlerMapping extends ApplicationObjectSupport
*/
public void setCorsConfigurations(Map<String, CorsConfiguration> corsConfigurations) {
Assert.notNull(corsConfigurations, "corsConfigurations must not be null");
this.corsConfigurationSource = new UrlBasedCorsConfigurationSource(this.patternParser);
((UrlBasedCorsConfigurationSource) this.corsConfigurationSource).setCorsConfigurations(corsConfigurations);
if (!corsConfigurations.isEmpty()) {
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(this.patternParser);
source.setCorsConfigurations(corsConfigurations);
this.corsConfigurationSource = source;
}
else {
this.corsConfigurationSource = null;
}
}
/**
@@ -175,12 +182,12 @@ public abstract class AbstractHandlerMapping extends ApplicationObjectSupport
if (logger.isDebugEnabled()) {
logger.debug(exchange.getLogPrefix() + "Mapped to " + handler);
}
if (CorsUtils.isCorsRequest(exchange.getRequest())) {
CorsConfiguration configA = this.corsConfigurationSource.getCorsConfiguration(exchange);
CorsConfiguration configB = getCorsConfiguration(handler, exchange);
CorsConfiguration config = (configA != null ? configA.combine(configB) : configB);
if (!getCorsProcessor().process(config, exchange) ||
CorsUtils.isPreFlightRequest(exchange.getRequest())) {
if (hasCorsConfigurationSource(handler)) {
ServerHttpRequest request = exchange.getRequest();
CorsConfiguration config = (this.corsConfigurationSource != null ? this.corsConfigurationSource.getCorsConfiguration(exchange) : null);
CorsConfiguration handlerConfig = getCorsConfiguration(handler, exchange);
config = (config != null ? config.combine(handlerConfig) : handlerConfig);
if (!this.corsProcessor.process(config, exchange) || CorsUtils.isPreFlightRequest(request)) {
return REQUEST_HANDLED_HANDLER;
}
}
@@ -200,6 +207,14 @@ public abstract class AbstractHandlerMapping extends ApplicationObjectSupport
*/
protected abstract Mono<?> getHandlerInternal(ServerWebExchange exchange);
/**
* 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

@@ -370,6 +370,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, ServerWebExchange exchange) {
CorsConfiguration corsConfig = super.getCorsConfiguration(handler, exchange);
@@ -451,6 +458,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.
@@ -31,7 +31,6 @@ import org.springframework.web.server.ServerWebExchange;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNotSame;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertSame;
/**
@@ -74,8 +73,7 @@ public class CorsUrlHandlerMappingTests {
Object actual = this.handlerMapping.getHandler(exchange).block();
assertNotNull(actual);
assertNotSame(this.welcomeController, actual);
assertNull(exchange.getResponse().getHeaders().getFirst(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN));
assertSame(this.welcomeController, actual);
}
@Test