Skip to content

Commit 55d7df2

Browse files
committed
Add authenticationFailureHandler method in OAuth2LoginSpec
Allow to customize the failure handler. Fixes gh-7051
1 parent ab6440d commit 55d7df2

File tree

3 files changed

+89
-11
lines changed

3 files changed

+89
-11
lines changed

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

Lines changed: 17 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,6 @@
5353
import org.springframework.security.authorization.AuthorizationDecision;
5454
import org.springframework.security.authorization.ReactiveAuthorizationManager;
5555
import org.springframework.security.core.Authentication;
56-
import org.springframework.security.core.AuthenticationException;
5756
import org.springframework.security.core.GrantedAuthority;
5857
import org.springframework.security.core.authority.AuthorityUtils;
5958
import org.springframework.security.core.userdetails.ReactiveUserDetailsService;
@@ -102,7 +101,6 @@
102101
import org.springframework.security.web.server.MatcherSecurityWebFilterChain;
103102
import org.springframework.security.web.server.SecurityWebFilterChain;
104103
import org.springframework.security.web.server.ServerAuthenticationEntryPoint;
105-
import org.springframework.security.web.server.WebFilterExchange;
106104
import org.springframework.security.web.server.authentication.AnonymousAuthenticationWebFilter;
107105
import org.springframework.security.web.server.authentication.AuthenticationWebFilter;
108106
import org.springframework.security.web.server.authentication.HttpBasicServerAuthenticationEntryPoint;
@@ -232,6 +230,7 @@
232230
* @author Rob Winch
233231
* @author Vedran Pavic
234232
* @author Rafiullah Hamedy
233+
* @author Eddú Meléndez
235234
* @since 5.0
236235
*/
237236
public class ServerHttpSecurity {
@@ -717,6 +716,8 @@ public class OAuth2LoginSpec {
717716

718717
private ServerAuthenticationSuccessHandler authenticationSuccessHandler = new RedirectServerAuthenticationSuccessHandler();
719718

719+
private ServerAuthenticationFailureHandler authenticationFailureHandler = (webFilterExchange, exception) -> Mono.error(exception);
720+
720721
/**
721722
* Configures the {@link ReactiveAuthenticationManager} to use. The default is
722723
* {@link OAuth2AuthorizationCodeReactiveAuthenticationManager}
@@ -742,6 +743,19 @@ public OAuth2LoginSpec authenticationSuccessHandler(ServerAuthenticationSuccessH
742743
return this;
743744
}
744745

746+
/**
747+
* The {@link ServerAuthenticationFailureHandler} used after authentication failure.
748+
*
749+
* @since 5.2
750+
* @param authenticationFailureHandler the failure handler to use
751+
* @return the {@link OAuth2LoginSpec} to customize
752+
*/
753+
public OAuth2LoginSpec authenticationFailureHandler(ServerAuthenticationFailureHandler authenticationFailureHandler) {
754+
Assert.notNull(authenticationFailureHandler, "authenticationFailureHandler cannot be null");
755+
this.authenticationFailureHandler = authenticationFailureHandler;
756+
return this;
757+
}
758+
745759
/**
746760
* Gets the {@link ReactiveAuthenticationManager} to use. First tries an explicitly configured manager, and
747761
* defaults to {@link OAuth2AuthorizationCodeReactiveAuthenticationManager}
@@ -859,13 +873,7 @@ protected void configure(ServerHttpSecurity http) {
859873
authenticationFilter.setServerAuthenticationConverter(getAuthenticationConverter(clientRegistrationRepository));
860874

861875
authenticationFilter.setAuthenticationSuccessHandler(this.authenticationSuccessHandler);
862-
authenticationFilter.setAuthenticationFailureHandler(new ServerAuthenticationFailureHandler() {
863-
@Override
864-
public Mono<Void> onAuthenticationFailure(WebFilterExchange webFilterExchange,
865-
AuthenticationException exception) {
866-
return Mono.error(exception);
867-
}
868-
});
876+
authenticationFilter.setAuthenticationFailureHandler(this.authenticationFailureHandler);
869877
authenticationFilter.setSecurityContextRepository(new WebSessionServerSecurityContextRepository());
870878

871879
MediaTypeServerWebExchangeMatcher htmlMatcher = new MediaTypeServerWebExchangeMatcher(

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

Lines changed: 64 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,14 @@
2525
import org.junit.Test;
2626
import org.mockito.stubbing.Answer;
2727
import org.openqa.selenium.WebDriver;
28+
29+
import org.springframework.security.core.AuthenticationException;
30+
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
31+
import org.springframework.security.oauth2.core.OAuth2Error;
2832
import org.springframework.security.web.server.WebFilterExchange;
33+
import org.springframework.security.web.server.authentication.RedirectServerAuthenticationFailureHandler;
2934
import org.springframework.security.web.server.authentication.RedirectServerAuthenticationSuccessHandler;
35+
import org.springframework.security.web.server.authentication.ServerAuthenticationFailureHandler;
3036
import org.springframework.security.web.server.authentication.ServerAuthenticationSuccessHandler;
3137
import reactor.core.publisher.Mono;
3238

@@ -97,6 +103,7 @@
97103

98104
/**
99105
* @author Rob Winch
106+
* @author Eddú Meléndez
100107
* @since 5.1
101108
*/
102109
public class OAuth2LoginTests {
@@ -233,6 +240,59 @@ public void oauth2LoginWhenCustomObjectsThenUsed() {
233240
verify(successHandler).onAuthenticationSuccess(any(), any());
234241
}
235242

243+
@Test
244+
public void oauth2LoginFailsWhenCustomObjectsThenUsed() {
245+
this.spring.register(OAuth2LoginWithSingleClientRegistrations.class,
246+
OAuth2LoginMockAuthenticationManagerConfig.class).autowire();
247+
248+
String redirectLocation = "/custom-redirect-location";
249+
String failureRedirectLocation = "/failure-redirect-location";
250+
251+
WebTestClient webTestClient = WebTestClientBuilder
252+
.bindToWebFilters(this.springSecurity)
253+
.build();
254+
255+
OAuth2LoginMockAuthenticationManagerConfig config = this.spring.getContext()
256+
.getBean(OAuth2LoginMockAuthenticationManagerConfig.class);
257+
ServerAuthenticationConverter converter = config.authenticationConverter;
258+
ReactiveAuthenticationManager manager = config.manager;
259+
ServerWebExchangeMatcher matcher = config.matcher;
260+
ServerOAuth2AuthorizationRequestResolver resolver = config.resolver;
261+
ServerAuthenticationSuccessHandler successHandler = config.successHandler;
262+
ServerAuthenticationFailureHandler failureHandler = config.failureHandler;
263+
264+
when(converter.convert(any())).thenReturn(Mono.just(new TestingAuthenticationToken("a", "b", "c")));
265+
when(manager.authenticate(any())).thenReturn(Mono.error(new OAuth2AuthenticationException(new OAuth2Error("error"), "message")));
266+
when(matcher.matches(any())).thenReturn(ServerWebExchangeMatcher.MatchResult.match());
267+
when(resolver.resolve(any())).thenReturn(Mono.empty());
268+
when(successHandler.onAuthenticationSuccess(any(), any())).thenAnswer((Answer<Mono<Void>>) invocation -> {
269+
WebFilterExchange webFilterExchange = invocation.getArgument(0);
270+
Authentication authentication = invocation.getArgument(1);
271+
272+
return new RedirectServerAuthenticationSuccessHandler(redirectLocation)
273+
.onAuthenticationSuccess(webFilterExchange, authentication);
274+
});
275+
when(failureHandler.onAuthenticationFailure(any(), any())).thenAnswer((Answer<Mono<Void>>) invocation -> {
276+
WebFilterExchange webFilterExchange = invocation.getArgument(0);
277+
AuthenticationException authenticationException = invocation.getArgument(1);
278+
279+
return new RedirectServerAuthenticationFailureHandler(failureRedirectLocation)
280+
.onAuthenticationFailure(webFilterExchange, authenticationException);
281+
});
282+
283+
webTestClient.get()
284+
.uri("/login/oauth2/code/github")
285+
.exchange()
286+
.expectStatus().is3xxRedirection()
287+
.expectHeader().valueEquals("Location", failureRedirectLocation);
288+
289+
verify(converter).convert(any());
290+
verify(manager).authenticate(any());
291+
verify(matcher).matches(any());
292+
verify(resolver).resolve(any());
293+
verify(failureHandler).onAuthenticationFailure(any(), any());
294+
}
295+
236296
@Configuration
237297
static class OAuth2LoginMockAuthenticationManagerConfig {
238298
ReactiveAuthenticationManager manager = mock(ReactiveAuthenticationManager.class);
@@ -245,6 +305,8 @@ static class OAuth2LoginMockAuthenticationManagerConfig {
245305

246306
ServerAuthenticationSuccessHandler successHandler = mock(ServerAuthenticationSuccessHandler.class);
247307

308+
ServerAuthenticationFailureHandler failureHandler = mock(ServerAuthenticationFailureHandler.class);
309+
248310
@Bean
249311
public SecurityWebFilterChain springSecurityFilter(ServerHttpSecurity http) {
250312
http
@@ -256,7 +318,8 @@ public SecurityWebFilterChain springSecurityFilter(ServerHttpSecurity http) {
256318
.authenticationManager(manager)
257319
.authenticationMatcher(matcher)
258320
.authorizationRequestResolver(resolver)
259-
.authenticationSuccessHandler(successHandler);
321+
.authenticationSuccessHandler(successHandler)
322+
.authenticationFailureHandler(failureHandler);
260323
return http.build();
261324
}
262325
}

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

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2018 the original author or authors.
2+
* Copyright 2002-2019 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.
@@ -18,6 +18,7 @@
1818

1919
/**
2020
* @author Rob Winch
21+
* @author Eddú Meléndez
2122
* @since 5.1
2223
*/
2324
public class TestOAuth2AuthorizationExchanges {
@@ -27,4 +28,10 @@ public static OAuth2AuthorizationExchange success() {
2728
OAuth2AuthorizationResponse response = TestOAuth2AuthorizationResponses.success().build();
2829
return new OAuth2AuthorizationExchange(request, response);
2930
}
31+
32+
public static OAuth2AuthorizationExchange failure() {
33+
OAuth2AuthorizationRequest request = TestOAuth2AuthorizationRequests.request().build();
34+
OAuth2AuthorizationResponse response = TestOAuth2AuthorizationResponses.error().build();
35+
return new OAuth2AuthorizationExchange(request, response);
36+
}
3037
}

0 commit comments

Comments
 (0)