Skip to content

Commit 2c836a1

Browse files
eddumelendezrwinch
authored andcommitted
Add authenticationFailureHandler method in OAuth2LoginSpec
Allow to customize the failure handler. Fixes gh-7051
1 parent b3da1e4 commit 2c836a1

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

+17-9
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,6 @@
5454
import org.springframework.security.authorization.AuthorizationDecision;
5555
import org.springframework.security.authorization.ReactiveAuthorizationManager;
5656
import org.springframework.security.core.Authentication;
57-
import org.springframework.security.core.AuthenticationException;
5857
import org.springframework.security.core.GrantedAuthority;
5958
import org.springframework.security.core.authority.AuthorityUtils;
6059
import org.springframework.security.core.userdetails.ReactiveUserDetailsService;
@@ -103,7 +102,6 @@
103102
import org.springframework.security.web.server.MatcherSecurityWebFilterChain;
104103
import org.springframework.security.web.server.SecurityWebFilterChain;
105104
import org.springframework.security.web.server.ServerAuthenticationEntryPoint;
106-
import org.springframework.security.web.server.WebFilterExchange;
107105
import org.springframework.security.web.server.authentication.AnonymousAuthenticationWebFilter;
108106
import org.springframework.security.web.server.authentication.AuthenticationWebFilter;
109107
import org.springframework.security.web.server.authentication.HttpBasicServerAuthenticationEntryPoint;
@@ -233,6 +231,7 @@
233231
* @author Rob Winch
234232
* @author Vedran Pavic
235233
* @author Rafiullah Hamedy
234+
* @author Eddú Meléndez
236235
* @since 5.0
237236
*/
238237
public class ServerHttpSecurity {
@@ -981,6 +980,8 @@ public class OAuth2LoginSpec {
981980

982981
private ServerAuthenticationSuccessHandler authenticationSuccessHandler = new RedirectServerAuthenticationSuccessHandler();
983982

983+
private ServerAuthenticationFailureHandler authenticationFailureHandler = (webFilterExchange, exception) -> Mono.error(exception);
984+
984985
/**
985986
* Configures the {@link ReactiveAuthenticationManager} to use. The default is
986987
* {@link OAuth2AuthorizationCodeReactiveAuthenticationManager}
@@ -1006,6 +1007,19 @@ public OAuth2LoginSpec authenticationSuccessHandler(ServerAuthenticationSuccessH
10061007
return this;
10071008
}
10081009

1010+
/**
1011+
* The {@link ServerAuthenticationFailureHandler} used after authentication failure.
1012+
*
1013+
* @since 5.2
1014+
* @param authenticationFailureHandler the failure handler to use
1015+
* @return the {@link OAuth2LoginSpec} to customize
1016+
*/
1017+
public OAuth2LoginSpec authenticationFailureHandler(ServerAuthenticationFailureHandler authenticationFailureHandler) {
1018+
Assert.notNull(authenticationFailureHandler, "authenticationFailureHandler cannot be null");
1019+
this.authenticationFailureHandler = authenticationFailureHandler;
1020+
return this;
1021+
}
1022+
10091023
/**
10101024
* Gets the {@link ReactiveAuthenticationManager} to use. First tries an explicitly configured manager, and
10111025
* defaults to {@link OAuth2AuthorizationCodeReactiveAuthenticationManager}
@@ -1123,13 +1137,7 @@ protected void configure(ServerHttpSecurity http) {
11231137
authenticationFilter.setServerAuthenticationConverter(getAuthenticationConverter(clientRegistrationRepository));
11241138

11251139
authenticationFilter.setAuthenticationSuccessHandler(this.authenticationSuccessHandler);
1126-
authenticationFilter.setAuthenticationFailureHandler(new ServerAuthenticationFailureHandler() {
1127-
@Override
1128-
public Mono<Void> onAuthenticationFailure(WebFilterExchange webFilterExchange,
1129-
AuthenticationException exception) {
1130-
return Mono.error(exception);
1131-
}
1132-
});
1140+
authenticationFilter.setAuthenticationFailureHandler(this.authenticationFailureHandler);
11331141
authenticationFilter.setSecurityContextRepository(new WebSessionServerSecurityContextRepository());
11341142

11351143
MediaTypeServerWebExchangeMatcher htmlMatcher = new MediaTypeServerWebExchangeMatcher(

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

+64-1
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

+8-1
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)