Skip to content

Commit 5afeaa3

Browse files
committed
WebFlux httpBasic() matches on XHR requests
Closes gh-9660
1 parent 7dc4de0 commit 5afeaa3

File tree

2 files changed

+45
-3
lines changed

2 files changed

+45
-3
lines changed

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

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@
4141
import org.springframework.core.annotation.AnnotationAwareOrderComparator;
4242
import org.springframework.core.convert.converter.Converter;
4343
import org.springframework.http.HttpMethod;
44+
import org.springframework.http.HttpStatus;
4445
import org.springframework.http.MediaType;
4546
import org.springframework.security.authentication.AbstractAuthenticationToken;
4647
import org.springframework.security.authentication.DelegatingReactiveAuthenticationManager;
@@ -111,6 +112,7 @@
111112
import org.springframework.security.web.server.authentication.AuthenticationConverterServerWebExchangeMatcher;
112113
import org.springframework.security.web.server.authentication.AuthenticationWebFilter;
113114
import org.springframework.security.web.server.authentication.HttpBasicServerAuthenticationEntryPoint;
115+
import org.springframework.security.web.server.authentication.HttpStatusServerEntryPoint;
114116
import org.springframework.security.web.server.authentication.ReactivePreAuthenticatedAuthenticationManager;
115117
import org.springframework.security.web.server.authentication.RedirectServerAuthenticationEntryPoint;
116118
import org.springframework.security.web.server.authentication.RedirectServerAuthenticationFailureHandler;
@@ -1910,13 +1912,25 @@ public ServerHttpSecurity disable() {
19101912
*/
19111913
public final class HttpBasicSpec {
19121914

1915+
private final ServerWebExchangeMatcher xhrMatcher = (exchange) -> Mono.just(exchange.getRequest().getHeaders())
1916+
.filter((h) -> h.getOrEmpty("X-Requested-With").contains("XMLHttpRequest"))
1917+
.flatMap((h) -> ServerWebExchangeMatcher.MatchResult.match())
1918+
.switchIfEmpty(ServerWebExchangeMatcher.MatchResult.notMatch());
1919+
19131920
private ReactiveAuthenticationManager authenticationManager;
19141921

19151922
private ServerSecurityContextRepository securityContextRepository;
19161923

1917-
private ServerAuthenticationEntryPoint entryPoint = new HttpBasicServerAuthenticationEntryPoint();
1924+
private ServerAuthenticationEntryPoint entryPoint;
19181925

19191926
private HttpBasicSpec() {
1927+
List<DelegateEntry> entryPoints = new ArrayList<>();
1928+
entryPoints
1929+
.add(new DelegateEntry(this.xhrMatcher, new HttpStatusServerEntryPoint(HttpStatus.UNAUTHORIZED)));
1930+
DelegatingServerAuthenticationEntryPoint defaultEntryPoint = new DelegatingServerAuthenticationEntryPoint(
1931+
entryPoints);
1932+
defaultEntryPoint.setDefaultEntryPoint(new HttpBasicServerAuthenticationEntryPoint());
1933+
this.entryPoint = defaultEntryPoint;
19201934
}
19211935

19221936
/**
@@ -1981,7 +1995,13 @@ protected void configure(ServerHttpSecurity http) {
19811995
MediaType.APPLICATION_OCTET_STREAM, MediaType.APPLICATION_XML, MediaType.MULTIPART_FORM_DATA,
19821996
MediaType.TEXT_XML);
19831997
restMatcher.setIgnoredMediaTypes(Collections.singleton(MediaType.ALL));
1984-
ServerHttpSecurity.this.defaultEntryPoints.add(new DelegateEntry(restMatcher, this.entryPoint));
1998+
ServerWebExchangeMatcher notHtmlMatcher = new NegatedServerWebExchangeMatcher(
1999+
new MediaTypeServerWebExchangeMatcher(MediaType.TEXT_HTML));
2000+
ServerWebExchangeMatcher restNotHtmlMatcher = new AndServerWebExchangeMatcher(
2001+
Arrays.asList(notHtmlMatcher, restMatcher));
2002+
ServerWebExchangeMatcher preferredMatcher = new OrServerWebExchangeMatcher(
2003+
Arrays.asList(this.xhrMatcher, restNotHtmlMatcher));
2004+
ServerHttpSecurity.this.defaultEntryPoints.add(new DelegateEntry(preferredMatcher, this.entryPoint));
19852005
AuthenticationWebFilter authenticationFilter = new AuthenticationWebFilter(this.authenticationManager);
19862006
authenticationFilter
19872007
.setAuthenticationFailureHandler(new ServerAuthenticationEntryPointFailureHandler(this.entryPoint));

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

Lines changed: 23 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.
@@ -32,6 +32,7 @@
3232
import reactor.core.publisher.Mono;
3333
import reactor.test.publisher.TestPublisher;
3434

35+
import org.springframework.http.HttpStatus;
3536
import org.springframework.security.authentication.ReactiveAuthenticationManager;
3637
import org.springframework.security.authentication.TestingAuthenticationToken;
3738
import org.springframework.security.config.annotation.web.reactive.ServerHttpSecurityConfigurationBuilder;
@@ -45,9 +46,11 @@
4546
import org.springframework.security.test.web.reactive.server.WebTestClientBuilder;
4647
import org.springframework.security.web.authentication.preauth.x509.X509PrincipalExtractor;
4748
import org.springframework.security.web.server.SecurityWebFilterChain;
49+
import org.springframework.security.web.server.ServerAuthenticationEntryPoint;
4850
import org.springframework.security.web.server.WebFilterChainProxy;
4951
import org.springframework.security.web.server.authentication.AnonymousAuthenticationWebFilterTests;
5052
import org.springframework.security.web.server.authentication.HttpBasicServerAuthenticationEntryPoint;
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.authentication.logout.DelegatingServerLogoutHandler;
5356
import org.springframework.security.web.server.authentication.logout.LogoutWebFilter;
@@ -184,6 +187,25 @@ public void basicWhenNoCredentialsThenUnauthorized() {
184187
// @formatter:on
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)