Skip to content

Commit 95752ef

Browse files
committed
Improve handling for pre-flight requests
1. Update the HandlerMapping contract to state that CORS checks are expected to be applied before returning a handler. 2. DispatcherHandler checks explicitly for pre-flight requests or CORS failed requests and skips handling for both. Technically no change since AbstractHandlerMapping already returns a NO_OP_HANDLER for those cases. The purpose however is for the DispatcherHandler to also guarantee more explicitly that no such handling can take place for such cases. As one consequence, this makes it possible to invoke the DispatcherHandler from anywhere in the WebFilter chain in order to "handle" a pre-flight request, and then skip the rest of the WebFilter chain. See gh-26257
1 parent 0ff50d6 commit 95752ef

File tree

4 files changed

+55
-11
lines changed

4 files changed

+55
-11
lines changed

spring-webflux/src/main/java/org/springframework/web/reactive/DispatcherHandler.java

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2019 the original author or authors.
2+
* Copyright 2002-2021 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -29,7 +29,10 @@
2929
import org.springframework.context.ApplicationContextAware;
3030
import org.springframework.core.annotation.AnnotationAwareOrderComparator;
3131
import org.springframework.http.HttpStatus;
32+
import org.springframework.http.server.reactive.ServerHttpRequest;
3233
import org.springframework.lang.Nullable;
34+
import org.springframework.util.ObjectUtils;
35+
import org.springframework.web.cors.reactive.CorsUtils;
3336
import org.springframework.web.server.ResponseStatusException;
3437
import org.springframework.web.server.ServerWebExchange;
3538
import org.springframework.web.server.WebHandler;
@@ -155,6 +158,12 @@ private <R> Mono<R> createNotFoundError() {
155158
}
156159

157160
private Mono<HandlerResult> invokeHandler(ServerWebExchange exchange, Object handler) {
161+
// No handling for CORS rejected requests and pre-flight requests
162+
ServerHttpRequest request = exchange.getRequest();
163+
HttpStatus status = exchange.getResponse().getStatusCode();
164+
if (ObjectUtils.nullSafeEquals(status, HttpStatus.FORBIDDEN) || CorsUtils.isPreFlightRequest(request)) {
165+
return Mono.empty();
166+
}
158167
if (this.handlerAdapters != null) {
159168
for (HandlerAdapter handlerAdapter : this.handlerAdapters) {
160169
if (handlerAdapter.supports(handler)) {

spring-webflux/src/main/java/org/springframework/web/reactive/HandlerMapping.java

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2016 the original author or authors.
2+
* Copyright 2002-2021 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -86,6 +86,11 @@ public interface HandlerMapping {
8686

8787
/**
8888
* Return a handler for this request.
89+
* <p>Before returning a handler, an implementing method should check for
90+
* CORS configuration associated with the handler, apply validation checks
91+
* based on it, and update the response accordingly. For pre-flight requests,
92+
* the same should be done based on the handler matching to the expected
93+
* actual request.
8994
* @param exchange current server exchange
9095
* @return a {@link Mono} that emits one value or none in case the request
9196
* cannot be resolved to a handler

spring-webflux/src/main/java/org/springframework/web/reactive/handler/AbstractHandlerMapping.java

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2020 the original author or authors.
2+
* Copyright 2002-2021 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -49,7 +49,7 @@
4949
public abstract class AbstractHandlerMapping extends ApplicationObjectSupport
5050
implements HandlerMapping, Ordered, BeanNameAware {
5151

52-
private static final WebHandler REQUEST_HANDLED_HANDLER = exchange -> Mono.empty();
52+
private static final WebHandler NO_OP_HANDLER = exchange -> Mono.empty();
5353

5454

5555
private final PathPatternParser patternParser;
@@ -184,14 +184,15 @@ public Mono<Object> getHandler(ServerWebExchange exchange) {
184184
}
185185
ServerHttpRequest request = exchange.getRequest();
186186
if (hasCorsConfigurationSource(handler) || CorsUtils.isPreFlightRequest(request)) {
187-
CorsConfiguration config = (this.corsConfigurationSource != null ? this.corsConfigurationSource.getCorsConfiguration(exchange) : null);
187+
CorsConfiguration config = (this.corsConfigurationSource != null ?
188+
this.corsConfigurationSource.getCorsConfiguration(exchange) : null);
188189
CorsConfiguration handlerConfig = getCorsConfiguration(handler, exchange);
189190
config = (config != null ? config.combine(handlerConfig) : handlerConfig);
190191
if (config != null) {
191192
config.validateAllowCredentials();
192193
}
193194
if (!this.corsProcessor.process(config, exchange) || CorsUtils.isPreFlightRequest(request)) {
194-
return REQUEST_HANDLED_HANDLER;
195+
return NO_OP_HANDLER;
195196
}
196197
}
197198
return handler;

spring-webflux/src/test/java/org/springframework/web/reactive/DispatcherHandlerTests.java

Lines changed: 34 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,10 @@
2828
import org.springframework.core.Ordered;
2929
import org.springframework.core.io.buffer.DataBuffer;
3030
import org.springframework.core.io.buffer.DefaultDataBufferFactory;
31+
import org.springframework.http.HttpHeaders;
32+
import org.springframework.web.reactive.result.SimpleHandlerAdapter;
3133
import org.springframework.web.server.ServerWebExchange;
34+
import org.springframework.web.server.WebHandler;
3235
import org.springframework.web.testfixture.http.server.reactive.MockServerHttpRequest;
3336
import org.springframework.web.testfixture.method.ResolvableMethod;
3437
import org.springframework.web.testfixture.server.MockServerWebExchange;
@@ -37,6 +40,7 @@
3740
import static org.mockito.ArgumentMatchers.any;
3841
import static org.mockito.BDDMockito.given;
3942
import static org.mockito.Mockito.mock;
43+
import static org.mockito.Mockito.verifyNoInteractions;
4044
import static org.mockito.Mockito.withSettings;
4145

4246
/**
@@ -50,7 +54,7 @@ public class DispatcherHandlerTests {
5054

5155

5256
@Test
53-
public void handlerMappingOrder() {
57+
void handlerMappingOrder() {
5458
HandlerMapping hm1 = mock(HandlerMapping.class, withSettings().extraInterfaces(Ordered.class));
5559
HandlerMapping hm2 = mock(HandlerMapping.class, withSettings().extraInterfaces(Ordered.class));
5660
given(((Ordered) hm1).getOrder()).willReturn(1);
@@ -65,13 +69,34 @@ public void handlerMappingOrder() {
6569
context.registerBean(HandlerResultHandler.class, StringHandlerResultHandler::new);
6670
context.refresh();
6771

68-
DispatcherHandler dispatcherHandler = new DispatcherHandler(context);
69-
7072
MockServerWebExchange exchange = MockServerWebExchange.from(MockServerHttpRequest.get("/"));
71-
dispatcherHandler.handle(exchange).block(Duration.ofSeconds(0));
73+
new DispatcherHandler(context).handle(exchange).block(Duration.ofSeconds(0));
74+
7275
assertThat(exchange.getResponse().getBodyAsString().block(Duration.ofSeconds(5))).isEqualTo("1");
7376
}
7477

78+
@Test
79+
void preFlightRequest() {
80+
WebHandler webHandler = mock(WebHandler.class);
81+
HandlerMapping handlerMapping = mock(HandlerMapping.class);
82+
given((handlerMapping).getHandler(any())).willReturn(Mono.just(webHandler));
83+
84+
StaticApplicationContext context = new StaticApplicationContext();
85+
context.registerBean("handlerMapping", HandlerMapping.class, () -> handlerMapping);
86+
context.registerBean(HandlerAdapter.class, SimpleHandlerAdapter::new);
87+
context.registerBean(HandlerResultHandler.class, StringHandlerResultHandler::new);
88+
context.refresh();
89+
90+
MockServerHttpRequest request = MockServerHttpRequest.options("/")
91+
.header(HttpHeaders.ORIGIN, "https://domain.com")
92+
.header(HttpHeaders.ACCESS_CONTROL_REQUEST_METHOD, "GET")
93+
.build();
94+
95+
MockServerWebExchange exchange = MockServerWebExchange.from(request);
96+
new DispatcherHandler(context).handle(exchange).block(Duration.ofSeconds(0));
97+
98+
verifyNoInteractions(webHandler);
99+
}
75100

76101
@SuppressWarnings("unused")
77102
private void handle() {}
@@ -101,7 +126,11 @@ public boolean supports(HandlerResult result) {
101126

102127
@Override
103128
public Mono<Void> handleResult(ServerWebExchange exchange, HandlerResult result) {
104-
byte[] bytes = ((String) result.getReturnValue()).getBytes(StandardCharsets.UTF_8);
129+
Object returnValue = result.getReturnValue();
130+
if (returnValue == null) {
131+
return Mono.empty();
132+
}
133+
byte[] bytes = ((String) returnValue).getBytes(StandardCharsets.UTF_8);
105134
DataBuffer dataBuffer = DefaultDataBufferFactory.sharedInstance.wrap(bytes);
106135
return exchange.getResponse().writeWith(Mono.just(dataBuffer));
107136
}

0 commit comments

Comments
 (0)