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
This commit is contained in:
Rossen Stoyanchev
2021-01-29 22:10:03 +00:00
parent 0ff50d6d9e
commit 95752ef1c9
4 changed files with 55 additions and 11 deletions

View File

@@ -28,7 +28,10 @@ import org.springframework.core.MethodParameter;
import org.springframework.core.Ordered;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.core.io.buffer.DefaultDataBufferFactory;
import org.springframework.http.HttpHeaders;
import org.springframework.web.reactive.result.SimpleHandlerAdapter;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.WebHandler;
import org.springframework.web.testfixture.http.server.reactive.MockServerHttpRequest;
import org.springframework.web.testfixture.method.ResolvableMethod;
import org.springframework.web.testfixture.server.MockServerWebExchange;
@@ -37,6 +40,7 @@ import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verifyNoInteractions;
import static org.mockito.Mockito.withSettings;
/**
@@ -50,7 +54,7 @@ public class DispatcherHandlerTests {
@Test
public void handlerMappingOrder() {
void handlerMappingOrder() {
HandlerMapping hm1 = mock(HandlerMapping.class, withSettings().extraInterfaces(Ordered.class));
HandlerMapping hm2 = mock(HandlerMapping.class, withSettings().extraInterfaces(Ordered.class));
given(((Ordered) hm1).getOrder()).willReturn(1);
@@ -65,13 +69,34 @@ public class DispatcherHandlerTests {
context.registerBean(HandlerResultHandler.class, StringHandlerResultHandler::new);
context.refresh();
DispatcherHandler dispatcherHandler = new DispatcherHandler(context);
MockServerWebExchange exchange = MockServerWebExchange.from(MockServerHttpRequest.get("/"));
dispatcherHandler.handle(exchange).block(Duration.ofSeconds(0));
new DispatcherHandler(context).handle(exchange).block(Duration.ofSeconds(0));
assertThat(exchange.getResponse().getBodyAsString().block(Duration.ofSeconds(5))).isEqualTo("1");
}
@Test
void preFlightRequest() {
WebHandler webHandler = mock(WebHandler.class);
HandlerMapping handlerMapping = mock(HandlerMapping.class);
given((handlerMapping).getHandler(any())).willReturn(Mono.just(webHandler));
StaticApplicationContext context = new StaticApplicationContext();
context.registerBean("handlerMapping", HandlerMapping.class, () -> handlerMapping);
context.registerBean(HandlerAdapter.class, SimpleHandlerAdapter::new);
context.registerBean(HandlerResultHandler.class, StringHandlerResultHandler::new);
context.refresh();
MockServerHttpRequest request = MockServerHttpRequest.options("/")
.header(HttpHeaders.ORIGIN, "https://domain.com")
.header(HttpHeaders.ACCESS_CONTROL_REQUEST_METHOD, "GET")
.build();
MockServerWebExchange exchange = MockServerWebExchange.from(request);
new DispatcherHandler(context).handle(exchange).block(Duration.ofSeconds(0));
verifyNoInteractions(webHandler);
}
@SuppressWarnings("unused")
private void handle() {}
@@ -101,7 +126,11 @@ public class DispatcherHandlerTests {
@Override
public Mono<Void> handleResult(ServerWebExchange exchange, HandlerResult result) {
byte[] bytes = ((String) result.getReturnValue()).getBytes(StandardCharsets.UTF_8);
Object returnValue = result.getReturnValue();
if (returnValue == null) {
return Mono.empty();
}
byte[] bytes = ((String) returnValue).getBytes(StandardCharsets.UTF_8);
DataBuffer dataBuffer = DefaultDataBufferFactory.sharedInstance.wrap(bytes);
return exchange.getResponse().writeWith(Mono.just(dataBuffer));
}