Skip to content

Commit c8b6dc3

Browse files
committed
WebFlux httpBasic() matches on XHR requests
Closes gh-9660
1 parent a85ce9c commit c8b6dc3

File tree

2 files changed

+48
-4
lines changed

2 files changed

+48
-4
lines changed

config/src/main/java/org/springframework/security/config/web/server/ServerHttpSecurity.java

+25-3
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
import java.util.function.Function;
3232
import java.util.function.Supplier;
3333

34+
import org.springframework.http.HttpStatus;
3435
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
3536
import org.springframework.security.oauth2.core.OAuth2AuthorizationException;
3637
import reactor.core.publisher.Mono;
@@ -110,6 +111,7 @@
110111
import org.springframework.security.web.server.authentication.AnonymousAuthenticationWebFilter;
111112
import org.springframework.security.web.server.authentication.AuthenticationWebFilter;
112113
import org.springframework.security.web.server.authentication.HttpBasicServerAuthenticationEntryPoint;
114+
import org.springframework.security.web.server.authentication.HttpStatusServerEntryPoint;
113115
import org.springframework.security.web.server.authentication.ReactivePreAuthenticatedAuthenticationManager;
114116
import org.springframework.security.web.server.authentication.RedirectServerAuthenticationEntryPoint;
115117
import org.springframework.security.web.server.authentication.RedirectServerAuthenticationFailureHandler;
@@ -2963,11 +2965,17 @@ private RequestCacheSpec() {}
29632965
* @see #httpBasic()
29642966
*/
29652967
public class HttpBasicSpec {
2968+
2969+
private final ServerWebExchangeMatcher xhrMatcher = (exchange) -> Mono.just(exchange.getRequest().getHeaders())
2970+
.filter((h) -> h.getOrEmpty("X-Requested-With").contains("XMLHttpRequest"))
2971+
.flatMap((h) -> ServerWebExchangeMatcher.MatchResult.match())
2972+
.switchIfEmpty(ServerWebExchangeMatcher.MatchResult.notMatch());
2973+
29662974
private ReactiveAuthenticationManager authenticationManager;
29672975

29682976
private ServerSecurityContextRepository securityContextRepository;
29692977

2970-
private ServerAuthenticationEntryPoint entryPoint = new HttpBasicServerAuthenticationEntryPoint();
2978+
private ServerAuthenticationEntryPoint entryPoint;
29712979

29722980
/**
29732981
* The {@link ReactiveAuthenticationManager} used to authenticate. Defaults to
@@ -3032,7 +3040,13 @@ protected void configure(ServerHttpSecurity http) {
30323040
MediaType.APPLICATION_OCTET_STREAM, MediaType.APPLICATION_XML,
30333041
MediaType.MULTIPART_FORM_DATA, MediaType.TEXT_XML);
30343042
restMatcher.setIgnoredMediaTypes(Collections.singleton(MediaType.ALL));
3035-
ServerHttpSecurity.this.defaultEntryPoints.add(new DelegateEntry(restMatcher, this.entryPoint));
3043+
ServerWebExchangeMatcher notHtmlMatcher = new NegatedServerWebExchangeMatcher(
3044+
new MediaTypeServerWebExchangeMatcher(MediaType.TEXT_HTML));
3045+
ServerWebExchangeMatcher restNotHtmlMatcher = new AndServerWebExchangeMatcher(
3046+
Arrays.asList(notHtmlMatcher, restMatcher));
3047+
ServerWebExchangeMatcher preferredMatcher = new OrServerWebExchangeMatcher(
3048+
Arrays.asList(this.xhrMatcher, restNotHtmlMatcher));
3049+
ServerHttpSecurity.this.defaultEntryPoints.add(new DelegateEntry(preferredMatcher, this.entryPoint));
30363050
AuthenticationWebFilter authenticationFilter = new AuthenticationWebFilter(
30373051
this.authenticationManager);
30383052
authenticationFilter.setAuthenticationFailureHandler(new ServerAuthenticationEntryPointFailureHandler(this.entryPoint));
@@ -3041,7 +3055,15 @@ protected void configure(ServerHttpSecurity http) {
30413055
http.addFilterAt(authenticationFilter, SecurityWebFiltersOrder.HTTP_BASIC);
30423056
}
30433057

3044-
private HttpBasicSpec() {}
3058+
private HttpBasicSpec() {
3059+
List<DelegateEntry> entryPoints = new ArrayList<>();
3060+
entryPoints
3061+
.add(new DelegateEntry(this.xhrMatcher, new HttpStatusServerEntryPoint(HttpStatus.UNAUTHORIZED)));
3062+
DelegatingServerAuthenticationEntryPoint defaultEntryPoint = new DelegatingServerAuthenticationEntryPoint(
3063+
entryPoints);
3064+
defaultEntryPoint.setDefaultEntryPoint(new HttpBasicServerAuthenticationEntryPoint());
3065+
this.entryPoint = defaultEntryPoint;
3066+
}
30453067
}
30463068

30473069
/**

config/src/test/java/org/springframework/security/config/web/server/ServerHttpSecurityTests.java

+23-1
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.
@@ -41,13 +41,16 @@
4141
import org.mockito.Mock;
4242
import org.mockito.junit.MockitoJUnitRunner;
4343

44+
import org.springframework.http.HttpStatus;
4445
import org.springframework.security.core.Authentication;
4546
import org.springframework.security.oauth2.client.registration.ReactiveClientRegistrationRepository;
4647
import org.springframework.security.oauth2.client.web.server.ServerAuthorizationRequestRepository;
4748
import org.springframework.security.oauth2.client.web.server.authentication.OAuth2LoginAuthenticationWebFilter;
4849
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest;
4950
import org.springframework.security.oauth2.core.endpoint.TestOAuth2AuthorizationRequests;
5051
import org.springframework.security.web.authentication.preauth.x509.X509PrincipalExtractor;
52+
import org.springframework.security.web.server.ServerAuthenticationEntryPoint;
53+
import org.springframework.security.web.server.authentication.HttpStatusServerEntryPoint;
5154
import org.springframework.security.web.server.authentication.ServerX509AuthenticationConverter;
5255
import org.springframework.security.web.server.savedrequest.ServerRequestCache;
5356
import org.springframework.security.web.server.savedrequest.WebSessionServerRequestCache;
@@ -184,6 +187,25 @@ public void basicWhenNoCredentialsThenUnauthorized() {
184187
.expectBody().isEmpty();
185188
}
186189

190+
@Test
191+
public void basicWhenXHRRequestThenUnauthorized() {
192+
ServerAuthenticationEntryPoint authenticationEntryPoint = spy(
193+
new HttpStatusServerEntryPoint(HttpStatus.UNAUTHORIZED));
194+
this.http.httpBasic().authenticationEntryPoint(authenticationEntryPoint);
195+
this.http.authorizeExchange().anyExchange().authenticated();
196+
WebTestClient client = buildClient();
197+
// @formatter:off
198+
client.get().uri("/")
199+
.header("X-Requested-With", "XMLHttpRequest")
200+
.exchange()
201+
.expectStatus().isUnauthorized()
202+
.expectHeader().doesNotExist("WWW-Authenticate")
203+
.expectHeader().valueMatches(HttpHeaders.CACHE_CONTROL, ".+")
204+
.expectBody().isEmpty();
205+
// @formatter:on
206+
verify(authenticationEntryPoint).commence(any(), any());
207+
}
208+
187209
@Test
188210
public void buildWhenServerWebExchangeFromContextThenFound() {
189211
SecurityWebFilterChain filter = this.http.build();

0 commit comments

Comments
 (0)