From 55d7df2660de1631d4bd07524d3751ccea1432f3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Edd=C3=BA=20Mel=C3=A9ndez?= Date: Thu, 4 Jul 2019 01:28:17 -0500 Subject: [PATCH] Add authenticationFailureHandler method in OAuth2LoginSpec Allow to customize the failure handler. Fixes gh-7051 --- .../config/web/server/ServerHttpSecurity.java | 26 +++++--- .../config/web/server/OAuth2LoginTests.java | 65 ++++++++++++++++++- .../TestOAuth2AuthorizationExchanges.java | 9 ++- 3 files changed, 89 insertions(+), 11 deletions(-) diff --git a/config/src/main/java/org/springframework/security/config/web/server/ServerHttpSecurity.java b/config/src/main/java/org/springframework/security/config/web/server/ServerHttpSecurity.java index 6d89dadc24d..790244be255 100644 --- a/config/src/main/java/org/springframework/security/config/web/server/ServerHttpSecurity.java +++ b/config/src/main/java/org/springframework/security/config/web/server/ServerHttpSecurity.java @@ -53,7 +53,6 @@ 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; @@ -102,7 +101,6 @@ 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; @@ -232,6 +230,7 @@ * @author Rob Winch * @author Vedran Pavic * @author Rafiullah Hamedy + * @author Eddú Meléndez * @since 5.0 */ public class ServerHttpSecurity { @@ -717,6 +716,8 @@ public class OAuth2LoginSpec { private ServerAuthenticationSuccessHandler authenticationSuccessHandler = new RedirectServerAuthenticationSuccessHandler(); + private ServerAuthenticationFailureHandler authenticationFailureHandler = (webFilterExchange, exception) -> Mono.error(exception); + /** * Configures the {@link ReactiveAuthenticationManager} to use. The default is * {@link OAuth2AuthorizationCodeReactiveAuthenticationManager} @@ -742,6 +743,19 @@ public OAuth2LoginSpec authenticationSuccessHandler(ServerAuthenticationSuccessH 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} @@ -859,13 +873,7 @@ protected void configure(ServerHttpSecurity http) { authenticationFilter.setServerAuthenticationConverter(getAuthenticationConverter(clientRegistrationRepository)); authenticationFilter.setAuthenticationSuccessHandler(this.authenticationSuccessHandler); - authenticationFilter.setAuthenticationFailureHandler(new ServerAuthenticationFailureHandler() { - @Override - public Mono onAuthenticationFailure(WebFilterExchange webFilterExchange, - AuthenticationException exception) { - return Mono.error(exception); - } - }); + authenticationFilter.setAuthenticationFailureHandler(this.authenticationFailureHandler); authenticationFilter.setSecurityContextRepository(new WebSessionServerSecurityContextRepository()); MediaTypeServerWebExchangeMatcher htmlMatcher = new MediaTypeServerWebExchangeMatcher( diff --git a/config/src/test/java/org/springframework/security/config/web/server/OAuth2LoginTests.java b/config/src/test/java/org/springframework/security/config/web/server/OAuth2LoginTests.java index 44bb47bd3e3..abd9171177c 100644 --- a/config/src/test/java/org/springframework/security/config/web/server/OAuth2LoginTests.java +++ b/config/src/test/java/org/springframework/security/config/web/server/OAuth2LoginTests.java @@ -25,8 +25,14 @@ 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 @@ /** * @author Rob Winch + * @author Eddú Meléndez * @since 5.1 */ public class OAuth2LoginTests { @@ -233,6 +240,59 @@ public void oauth2LoginWhenCustomObjectsThenUsed() { 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>) 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>) 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 @@ static class OAuth2LoginMockAuthenticationManagerConfig { ServerAuthenticationSuccessHandler successHandler = mock(ServerAuthenticationSuccessHandler.class); + ServerAuthenticationFailureHandler failureHandler = mock(ServerAuthenticationFailureHandler.class); + @Bean public SecurityWebFilterChain springSecurityFilter(ServerHttpSecurity http) { http @@ -256,7 +318,8 @@ public SecurityWebFilterChain springSecurityFilter(ServerHttpSecurity http) { .authenticationManager(manager) .authenticationMatcher(matcher) .authorizationRequestResolver(resolver) - .authenticationSuccessHandler(successHandler); + .authenticationSuccessHandler(successHandler) + .authenticationFailureHandler(failureHandler); return http.build(); } } diff --git a/oauth2/oauth2-core/src/test/java/org/springframework/security/oauth2/core/endpoint/TestOAuth2AuthorizationExchanges.java b/oauth2/oauth2-core/src/test/java/org/springframework/security/oauth2/core/endpoint/TestOAuth2AuthorizationExchanges.java index c353afd9d79..98fdbec1dc4 100644 --- a/oauth2/oauth2-core/src/test/java/org/springframework/security/oauth2/core/endpoint/TestOAuth2AuthorizationExchanges.java +++ b/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 @@ /** * @author Rob Winch + * @author Eddú Meléndez * @since 5.1 */ public class TestOAuth2AuthorizationExchanges { @@ -27,4 +28,10 @@ public static OAuth2AuthorizationExchange success() { 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); + } }