2
0
Эх сурвалжийг харах

Add authenticationFailureHandler method in OAuth2LoginSpec

Allow to customize the failure handler.

Fixes gh-7051
Eddú Meléndez 6 жил өмнө
parent
commit
2c836a171a

+ 17 - 9
config/src/main/java/org/springframework/security/config/web/server/ServerHttpSecurity.java

@@ -54,7 +54,6 @@ import org.springframework.security.authorization.AuthorityReactiveAuthorization
 import org.springframework.security.authorization.AuthorizationDecision;
 import org.springframework.security.authorization.ReactiveAuthorizationManager;
 import org.springframework.security.core.Authentication;
-import org.springframework.security.core.AuthenticationException;
 import org.springframework.security.core.GrantedAuthority;
 import org.springframework.security.core.authority.AuthorityUtils;
 import org.springframework.security.core.userdetails.ReactiveUserDetailsService;
@@ -103,7 +102,6 @@ import org.springframework.security.web.server.DelegatingServerAuthenticationEnt
 import org.springframework.security.web.server.MatcherSecurityWebFilterChain;
 import org.springframework.security.web.server.SecurityWebFilterChain;
 import org.springframework.security.web.server.ServerAuthenticationEntryPoint;
-import org.springframework.security.web.server.WebFilterExchange;
 import org.springframework.security.web.server.authentication.AnonymousAuthenticationWebFilter;
 import org.springframework.security.web.server.authentication.AuthenticationWebFilter;
 import org.springframework.security.web.server.authentication.HttpBasicServerAuthenticationEntryPoint;
@@ -233,6 +231,7 @@ import static org.springframework.security.web.server.util.matcher.ServerWebExch
  * @author Rob Winch
  * @author Vedran Pavic
  * @author Rafiullah Hamedy
+ * @author Eddú Meléndez
  * @since 5.0
  */
 public class ServerHttpSecurity {
@@ -981,6 +980,8 @@ public class ServerHttpSecurity {
 
 		private ServerAuthenticationSuccessHandler authenticationSuccessHandler = new RedirectServerAuthenticationSuccessHandler();
 
+		private ServerAuthenticationFailureHandler authenticationFailureHandler = (webFilterExchange, exception) -> Mono.error(exception);
+
 		/**
 		 * Configures the {@link ReactiveAuthenticationManager} to use. The default is
 		 * {@link OAuth2AuthorizationCodeReactiveAuthenticationManager}
@@ -1006,6 +1007,19 @@ public class ServerHttpSecurity {
 			return this;
 		}
 
+		/**
+		 * The {@link ServerAuthenticationFailureHandler} used after authentication failure.
+		 *
+		 * @since 5.2
+		 * @param authenticationFailureHandler the failure handler to use
+		 * @return the {@link OAuth2LoginSpec} to customize
+		 */
+		public OAuth2LoginSpec authenticationFailureHandler(ServerAuthenticationFailureHandler authenticationFailureHandler) {
+			Assert.notNull(authenticationFailureHandler, "authenticationFailureHandler cannot be null");
+			this.authenticationFailureHandler = authenticationFailureHandler;
+			return this;
+		}
+
 		/**
 		 * Gets the {@link ReactiveAuthenticationManager} to use. First tries an explicitly configured manager, and
 		 * defaults to {@link OAuth2AuthorizationCodeReactiveAuthenticationManager}
@@ -1123,13 +1137,7 @@ public class ServerHttpSecurity {
 			authenticationFilter.setServerAuthenticationConverter(getAuthenticationConverter(clientRegistrationRepository));
 
 			authenticationFilter.setAuthenticationSuccessHandler(this.authenticationSuccessHandler);
-			authenticationFilter.setAuthenticationFailureHandler(new ServerAuthenticationFailureHandler() {
-				@Override
-				public Mono<Void> onAuthenticationFailure(WebFilterExchange webFilterExchange,
-						AuthenticationException exception) {
-					return Mono.error(exception);
-				}
-			});
+			authenticationFilter.setAuthenticationFailureHandler(this.authenticationFailureHandler);
 			authenticationFilter.setSecurityContextRepository(new WebSessionServerSecurityContextRepository());
 
 			MediaTypeServerWebExchangeMatcher htmlMatcher = new MediaTypeServerWebExchangeMatcher(

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

@@ -25,8 +25,14 @@ import org.junit.Rule;
 import org.junit.Test;
 import org.mockito.stubbing.Answer;
 import org.openqa.selenium.WebDriver;
+
+import org.springframework.security.core.AuthenticationException;
+import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
+import org.springframework.security.oauth2.core.OAuth2Error;
 import org.springframework.security.web.server.WebFilterExchange;
+import org.springframework.security.web.server.authentication.RedirectServerAuthenticationFailureHandler;
 import org.springframework.security.web.server.authentication.RedirectServerAuthenticationSuccessHandler;
+import org.springframework.security.web.server.authentication.ServerAuthenticationFailureHandler;
 import org.springframework.security.web.server.authentication.ServerAuthenticationSuccessHandler;
 import reactor.core.publisher.Mono;
 
@@ -97,6 +103,7 @@ import static org.mockito.Mockito.when;
 
 /**
  * @author Rob Winch
+ * @author Eddú Meléndez
  * @since 5.1
  */
 public class OAuth2LoginTests {
@@ -233,6 +240,59 @@ public class OAuth2LoginTests {
 		verify(successHandler).onAuthenticationSuccess(any(), any());
 	}
 
+	@Test
+	public void oauth2LoginFailsWhenCustomObjectsThenUsed() {
+		this.spring.register(OAuth2LoginWithSingleClientRegistrations.class,
+				OAuth2LoginMockAuthenticationManagerConfig.class).autowire();
+
+		String redirectLocation = "/custom-redirect-location";
+		String failureRedirectLocation = "/failure-redirect-location";
+
+		WebTestClient webTestClient = WebTestClientBuilder
+				.bindToWebFilters(this.springSecurity)
+				.build();
+
+		OAuth2LoginMockAuthenticationManagerConfig config = this.spring.getContext()
+				.getBean(OAuth2LoginMockAuthenticationManagerConfig.class);
+		ServerAuthenticationConverter converter = config.authenticationConverter;
+		ReactiveAuthenticationManager manager = config.manager;
+		ServerWebExchangeMatcher matcher = config.matcher;
+		ServerOAuth2AuthorizationRequestResolver resolver = config.resolver;
+		ServerAuthenticationSuccessHandler successHandler = config.successHandler;
+		ServerAuthenticationFailureHandler failureHandler = config.failureHandler;
+
+		when(converter.convert(any())).thenReturn(Mono.just(new TestingAuthenticationToken("a", "b", "c")));
+		when(manager.authenticate(any())).thenReturn(Mono.error(new OAuth2AuthenticationException(new OAuth2Error("error"), "message")));
+		when(matcher.matches(any())).thenReturn(ServerWebExchangeMatcher.MatchResult.match());
+		when(resolver.resolve(any())).thenReturn(Mono.empty());
+		when(successHandler.onAuthenticationSuccess(any(), any())).thenAnswer((Answer<Mono<Void>>) invocation -> {
+			WebFilterExchange webFilterExchange = invocation.getArgument(0);
+			Authentication authentication = invocation.getArgument(1);
+
+			return new RedirectServerAuthenticationSuccessHandler(redirectLocation)
+					.onAuthenticationSuccess(webFilterExchange, authentication);
+		});
+		when(failureHandler.onAuthenticationFailure(any(), any())).thenAnswer((Answer<Mono<Void>>) invocation -> {
+			WebFilterExchange webFilterExchange = invocation.getArgument(0);
+			AuthenticationException authenticationException = invocation.getArgument(1);
+
+			return new RedirectServerAuthenticationFailureHandler(failureRedirectLocation)
+					.onAuthenticationFailure(webFilterExchange, authenticationException);
+		});
+
+		webTestClient.get()
+				.uri("/login/oauth2/code/github")
+				.exchange()
+				.expectStatus().is3xxRedirection()
+				.expectHeader().valueEquals("Location", failureRedirectLocation);
+
+		verify(converter).convert(any());
+		verify(manager).authenticate(any());
+		verify(matcher).matches(any());
+		verify(resolver).resolve(any());
+		verify(failureHandler).onAuthenticationFailure(any(), any());
+	}
+
 	@Configuration
 	static class OAuth2LoginMockAuthenticationManagerConfig {
 		ReactiveAuthenticationManager manager = mock(ReactiveAuthenticationManager.class);
@@ -245,6 +305,8 @@ public class OAuth2LoginTests {
 
 		ServerAuthenticationSuccessHandler successHandler = mock(ServerAuthenticationSuccessHandler.class);
 
+		ServerAuthenticationFailureHandler failureHandler = mock(ServerAuthenticationFailureHandler.class);
+
 		@Bean
 		public SecurityWebFilterChain springSecurityFilter(ServerHttpSecurity http) {
 			http
@@ -256,7 +318,8 @@ public class OAuth2LoginTests {
 					.authenticationManager(manager)
 					.authenticationMatcher(matcher)
 					.authorizationRequestResolver(resolver)
-					.authenticationSuccessHandler(successHandler);
+					.authenticationSuccessHandler(successHandler)
+					.authenticationFailureHandler(failureHandler);
 			return http.build();
 		}
 	}

+ 8 - 1
oauth2/oauth2-core/src/test/java/org/springframework/security/oauth2/core/endpoint/TestOAuth2AuthorizationExchanges.java

@@ -1,5 +1,5 @@
 /*
- * Copyright 2002-2018 the original author or authors.
+ * Copyright 2002-2019 the original author or authors.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -18,6 +18,7 @@ package org.springframework.security.oauth2.core.endpoint;
 
 /**
  * @author Rob Winch
+ * @author Eddú Meléndez
  * @since 5.1
  */
 public class TestOAuth2AuthorizationExchanges {
@@ -27,4 +28,10 @@ public class TestOAuth2AuthorizationExchanges {
 		OAuth2AuthorizationResponse response = TestOAuth2AuthorizationResponses.success().build();
 		return new OAuth2AuthorizationExchange(request, response);
 	}
+
+	public static OAuth2AuthorizationExchange failure() {
+		OAuth2AuthorizationRequest request = TestOAuth2AuthorizationRequests.request().build();
+		OAuth2AuthorizationResponse response = TestOAuth2AuthorizationResponses.error().build();
+		return new OAuth2AuthorizationExchange(request, response);
+	}
 }