Skip to content

Commit 6725b13

Browse files
committed
WebFlux httpBasic() matches on XHR requests
Closes gh-9660
1 parent b97e93a commit 6725b13

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

+22-2
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;
@@ -113,6 +114,7 @@
113114
import org.springframework.security.web.server.authentication.AuthenticationConverterServerWebExchangeMatcher;
114115
import org.springframework.security.web.server.authentication.AuthenticationWebFilter;
115116
import org.springframework.security.web.server.authentication.HttpBasicServerAuthenticationEntryPoint;
117+
import org.springframework.security.web.server.authentication.HttpStatusServerEntryPoint;
116118
import org.springframework.security.web.server.authentication.ReactivePreAuthenticatedAuthenticationManager;
117119
import org.springframework.security.web.server.authentication.RedirectServerAuthenticationEntryPoint;
118120
import org.springframework.security.web.server.authentication.RedirectServerAuthenticationFailureHandler;
@@ -1911,13 +1913,25 @@ public ServerHttpSecurity disable() {
19111913
*/
19121914
public final class HttpBasicSpec {
19131915

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

19161923
private ServerSecurityContextRepository securityContextRepository;
19171924

1918-
private ServerAuthenticationEntryPoint entryPoint = new HttpBasicServerAuthenticationEntryPoint();
1925+
private ServerAuthenticationEntryPoint entryPoint;
19191926

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

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

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.
@@ -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)