Browse Source

oauth2Login WebFlux does not auto-redirect for XHR request

Fixes gh-8118
Joe Grandja 5 years ago
parent
commit
e27e548215

+ 45 - 13
config/src/main/java/org/springframework/security/config/web/server/ServerHttpSecurity.java

@@ -1,5 +1,5 @@
 /*
 /*
- * Copyright 2002-2019 the original author or authors.
+ * Copyright 2002-2020 the original author or authors.
  *
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
  * you may not use this file except in compliance with the License.
@@ -1180,22 +1180,54 @@ public class ServerHttpSecurity {
 			authenticationFilter.setAuthenticationFailureHandler(getAuthenticationFailureHandler());
 			authenticationFilter.setAuthenticationFailureHandler(getAuthenticationFailureHandler());
 			authenticationFilter.setSecurityContextRepository(this.securityContextRepository);
 			authenticationFilter.setSecurityContextRepository(this.securityContextRepository);
 
 
-			MediaTypeServerWebExchangeMatcher htmlMatcher = new MediaTypeServerWebExchangeMatcher(
-					MediaType.TEXT_HTML);
-			htmlMatcher.setIgnoredMediaTypes(Collections.singleton(MediaType.ALL));
+			setDefaultEntryPoints(http);
+
+			http.addFilterAt(oauthRedirectFilter, SecurityWebFiltersOrder.HTTP_BASIC);
+			http.addFilterAt(authenticationFilter, SecurityWebFiltersOrder.AUTHENTICATION);
+		}
+
+		private void setDefaultEntryPoints(ServerHttpSecurity http) {
+			String defaultLoginPage = "/login";
 			Map<String, String> urlToText = http.oauth2Login.getLinks();
 			Map<String, String> urlToText = http.oauth2Login.getLinks();
-			String authenticationEntryPointRedirectPath;
+			String providerLoginPage = null;
 			if (urlToText.size() == 1) {
 			if (urlToText.size() == 1) {
-				authenticationEntryPointRedirectPath = urlToText.keySet().iterator().next();
-			} else {
-				authenticationEntryPointRedirectPath = "/login";
+				providerLoginPage = urlToText.keySet().iterator().next();
 			}
 			}
-			RedirectServerAuthenticationEntryPoint entryPoint = new RedirectServerAuthenticationEntryPoint(authenticationEntryPointRedirectPath);
-			entryPoint.setRequestCache(http.requestCache.requestCache);
-			http.defaultEntryPoints.add(new DelegateEntry(htmlMatcher, entryPoint));
 
 
-			http.addFilterAt(oauthRedirectFilter, SecurityWebFiltersOrder.HTTP_BASIC);
-			http.addFilterAt(authenticationFilter, SecurityWebFiltersOrder.AUTHENTICATION);
+			MediaTypeServerWebExchangeMatcher htmlMatcher = new MediaTypeServerWebExchangeMatcher(
+					MediaType.APPLICATION_XHTML_XML, new MediaType("image", "*"),
+					MediaType.TEXT_HTML, MediaType.TEXT_PLAIN);
+			htmlMatcher.setIgnoredMediaTypes(Collections.singleton(MediaType.ALL));
+
+			ServerWebExchangeMatcher xhrMatcher = exchange -> {
+				if (exchange.getRequest().getHeaders().getOrEmpty("X-Requested-With").contains("XMLHttpRequest")) {
+					return ServerWebExchangeMatcher.MatchResult.match();
+				}
+				return ServerWebExchangeMatcher.MatchResult.notMatch();
+			};
+			ServerWebExchangeMatcher notXhrMatcher = new NegatedServerWebExchangeMatcher(xhrMatcher);
+
+			ServerWebExchangeMatcher defaultEntryPointMatcher = new AndServerWebExchangeMatcher(
+					notXhrMatcher, htmlMatcher);
+
+			if (providerLoginPage != null) {
+				ServerWebExchangeMatcher loginPageMatcher = new PathPatternParserServerWebExchangeMatcher(defaultLoginPage);
+				ServerWebExchangeMatcher faviconMatcher = new PathPatternParserServerWebExchangeMatcher("/favicon.ico");
+				ServerWebExchangeMatcher defaultLoginPageMatcher = new AndServerWebExchangeMatcher(
+						new OrServerWebExchangeMatcher(loginPageMatcher, faviconMatcher), defaultEntryPointMatcher);
+
+				ServerWebExchangeMatcher matcher = new AndServerWebExchangeMatcher(
+						notXhrMatcher, new NegatedServerWebExchangeMatcher(defaultLoginPageMatcher));
+				RedirectServerAuthenticationEntryPoint entryPoint =
+						new RedirectServerAuthenticationEntryPoint(providerLoginPage);
+				entryPoint.setRequestCache(http.requestCache.requestCache);
+				http.defaultEntryPoints.add(new DelegateEntry(matcher, entryPoint));
+			}
+
+			RedirectServerAuthenticationEntryPoint defaultEntryPoint =
+					new RedirectServerAuthenticationEntryPoint(defaultLoginPage);
+			defaultEntryPoint.setRequestCache(http.requestCache.requestCache);
+			http.defaultEntryPoints.add(new DelegateEntry(defaultEntryPointMatcher, defaultEntryPoint));
 		}
 		}
 
 
 		private ServerAuthenticationSuccessHandler getAuthenticationSuccessHandler(ServerHttpSecurity http) {
 		private ServerAuthenticationSuccessHandler getAuthenticationSuccessHandler(ServerHttpSecurity http) {

+ 18 - 1
config/src/test/java/org/springframework/security/config/web/server/OAuth2LoginTests.java

@@ -1,5 +1,5 @@
 /*
 /*
- * Copyright 2002-2019 the original author or authors.
+ * Copyright 2002-2020 the original author or authors.
  *
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
  * you may not use this file except in compliance with the License.
@@ -24,6 +24,7 @@ import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.context.ApplicationContext;
 import org.springframework.context.ApplicationContext;
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration;
 import org.springframework.context.annotation.Configuration;
+import org.springframework.http.HttpHeaders;
 import org.springframework.security.authentication.ReactiveAuthenticationManager;
 import org.springframework.security.authentication.ReactiveAuthenticationManager;
 import org.springframework.security.authentication.TestingAuthenticationToken;
 import org.springframework.security.authentication.TestingAuthenticationToken;
 import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity;
 import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity;
@@ -185,6 +186,22 @@ public class OAuth2LoginTests {
 		assertThat(driver.getCurrentUrl()).startsWith("https://github.com/login/oauth/authorize");
 		assertThat(driver.getCurrentUrl()).startsWith("https://github.com/login/oauth/authorize");
 	}
 	}
 
 
+	// gh-8118
+	@Test
+	public void defaultLoginPageWithSingleClientRegistrationAndXhrRequestThenDoesNotRedirectForAuthorization() {
+		this.spring.register(OAuth2LoginWithSingleClientRegistrations.class, WebFluxConfig.class).autowire();
+
+		this.client.get()
+				.uri("/")
+				.header("X-Requested-With", "XMLHttpRequest")
+				.exchange()
+				.expectStatus().is3xxRedirection()
+				.expectHeader().valueEquals(HttpHeaders.LOCATION, "/login");
+	}
+
+	@EnableWebFlux
+	static class WebFluxConfig { }
+
 	@EnableWebFluxSecurity
 	@EnableWebFluxSecurity
 	static class OAuth2LoginWithSingleClientRegistrations {
 	static class OAuth2LoginWithSingleClientRegistrations {
 		@Bean
 		@Bean