Skip to content

Commit 459e8f1

Browse files
committed
WebFlux oauth2Login() redirects on failed authentication
Fixes gh-5562 gh-6484
1 parent 450a20a commit 459e8f1

File tree

2 files changed

+98
-19
lines changed

2 files changed

+98
-19
lines changed

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

Lines changed: 3 additions & 13 deletions
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.
@@ -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.oauth2.client.InMemoryReactiveOAuth2AuthorizedClientService;
5857
import org.springframework.security.oauth2.client.ReactiveOAuth2AuthorizedClientService;
5958
import org.springframework.security.oauth2.client.authentication.OAuth2AuthorizationCodeReactiveAuthenticationManager;
@@ -89,7 +88,6 @@
8988
import org.springframework.security.web.server.MatcherSecurityWebFilterChain;
9089
import org.springframework.security.web.server.SecurityWebFilterChain;
9190
import org.springframework.security.web.server.ServerAuthenticationEntryPoint;
92-
import org.springframework.security.web.server.WebFilterExchange;
9391
import org.springframework.security.web.server.authentication.AuthenticationWebFilter;
9492
import org.springframework.security.web.server.authentication.HttpBasicServerAuthenticationEntryPoint;
9593
import org.springframework.security.web.server.authentication.RedirectServerAuthenticationEntryPoint;
@@ -619,16 +617,8 @@ protected void configure(ServerHttpSecurity http) {
619617
AuthenticationWebFilter authenticationFilter = new OAuth2LoginAuthenticationWebFilter(manager, authorizedClientRepository);
620618
authenticationFilter.setRequiresAuthenticationMatcher(createAttemptAuthenticationRequestMatcher());
621619
authenticationFilter.setServerAuthenticationConverter(getAuthenticationConverter(clientRegistrationRepository));
622-
RedirectServerAuthenticationSuccessHandler redirectHandler = new RedirectServerAuthenticationSuccessHandler();
623-
624-
authenticationFilter.setAuthenticationSuccessHandler(redirectHandler);
625-
authenticationFilter.setAuthenticationFailureHandler(new ServerAuthenticationFailureHandler() {
626-
@Override
627-
public Mono<Void> onAuthenticationFailure(WebFilterExchange webFilterExchange,
628-
AuthenticationException exception) {
629-
return Mono.error(exception);
630-
}
631-
});
620+
authenticationFilter.setAuthenticationSuccessHandler(new RedirectServerAuthenticationSuccessHandler());
621+
authenticationFilter.setAuthenticationFailureHandler(new RedirectServerAuthenticationFailureHandler("/login?error"));
632622
authenticationFilter.setSecurityContextRepository(new WebSessionServerSecurityContextRepository());
633623

634624
MediaTypeServerWebExchangeMatcher htmlMatcher = new MediaTypeServerWebExchangeMatcher(

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

Lines changed: 95 additions & 6 deletions
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.
@@ -34,13 +34,26 @@
3434
import org.springframework.security.config.oauth2.client.CommonOAuth2Provider;
3535
import org.springframework.security.config.test.SpringTestRule;
3636
import org.springframework.security.htmlunit.server.WebTestClientHtmlUnitDriverBuilder;
37+
import org.springframework.security.oauth2.client.authentication.OAuth2AuthorizationCodeAuthenticationToken;
3738
import org.springframework.security.oauth2.client.authentication.OAuth2LoginAuthenticationToken;
39+
import org.springframework.security.oauth2.client.endpoint.OAuth2AuthorizationCodeGrantRequest;
40+
import org.springframework.security.oauth2.client.endpoint.ReactiveOAuth2AccessTokenResponseClient;
41+
import org.springframework.security.oauth2.client.oidc.authentication.OidcAuthorizationCodeReactiveAuthenticationManager;
42+
import org.springframework.security.oauth2.client.oidc.userinfo.OidcUserRequest;
3843
import org.springframework.security.oauth2.client.registration.ClientRegistration;
3944
import org.springframework.security.oauth2.client.registration.InMemoryReactiveClientRegistrationRepository;
45+
import org.springframework.security.oauth2.client.userinfo.ReactiveOAuth2UserService;
4046
import org.springframework.security.oauth2.core.OAuth2AccessToken;
47+
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
48+
import org.springframework.security.oauth2.core.OAuth2Error;
4149
import org.springframework.security.oauth2.core.TestOAuth2AccessTokens;
4250
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationExchange;
51+
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest;
52+
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationResponse;
4353
import org.springframework.security.oauth2.core.endpoint.TestOAuth2AuthorizationExchanges;
54+
import org.springframework.security.oauth2.core.endpoint.TestOAuth2AuthorizationRequests;
55+
import org.springframework.security.oauth2.core.endpoint.TestOAuth2AuthorizationResponses;
56+
import org.springframework.security.oauth2.core.oidc.user.OidcUser;
4457
import org.springframework.security.oauth2.core.user.OAuth2User;
4558
import org.springframework.security.oauth2.core.user.TestOAuth2Users;
4659
import org.springframework.security.test.web.reactive.server.WebTestClientBuilder;
@@ -54,6 +67,9 @@
5467

5568
import reactor.core.publisher.Mono;
5669

70+
import java.time.Duration;
71+
import java.time.Instant;
72+
5773
/**
5874
* @author Rob Winch
5975
* @since 5.1
@@ -72,6 +88,12 @@ public class OAuth2LoginTests {
7288
.clientSecret("secret")
7389
.build();
7490

91+
private static ClientRegistration google = CommonOAuth2Provider.GOOGLE
92+
.getBuilder("google")
93+
.clientId("client")
94+
.clientSecret("secret")
95+
.build();
96+
7597
@Test
7698
public void defaultLoginPageWithMultipleClientRegistrationsThenLinks() {
7799
this.spring.register(OAuth2LoginWithMulitpleClientRegistrations.class).autowire();
@@ -97,11 +119,6 @@ public void defaultLoginPageWithMultipleClientRegistrationsThenLinks() {
97119
static class OAuth2LoginWithMulitpleClientRegistrations {
98120
@Bean
99121
InMemoryReactiveClientRegistrationRepository clientRegistrationRepository() {
100-
ClientRegistration google = CommonOAuth2Provider.GOOGLE
101-
.getBuilder("google")
102-
.clientId("client")
103-
.clientSecret("secret")
104-
.build();
105122
return new InMemoryReactiveClientRegistrationRepository(github, google);
106123
}
107124
}
@@ -182,6 +199,78 @@ public SecurityWebFilterChain springSecurityFilter(ServerHttpSecurity http) {
182199
}
183200
}
184201

202+
// gh-5562
203+
@Test
204+
public void oauth2LoginWhenAccessTokenRequestFailsThenDefaultRedirectToLogin() {
205+
this.spring.register(OAuth2LoginWithMulitpleClientRegistrations.class,
206+
OAuth2LoginWithCustomBeansConfig.class).autowire();
207+
208+
WebTestClient webTestClient = WebTestClientBuilder
209+
.bindToWebFilters(this.springSecurity)
210+
.build();
211+
212+
OAuth2AuthorizationRequest request = TestOAuth2AuthorizationRequests.request().scope("openid").build();
213+
OAuth2AuthorizationResponse response = TestOAuth2AuthorizationResponses.success().build();
214+
OAuth2AuthorizationExchange exchange = new OAuth2AuthorizationExchange(request, response);
215+
OAuth2AccessToken accessToken = new OAuth2AccessToken(
216+
OAuth2AccessToken.TokenType.BEARER, "openid", Instant.now(), Instant.now().plus(Duration.ofDays(1)));
217+
OAuth2AuthorizationCodeAuthenticationToken authenticationToken =
218+
new OAuth2AuthorizationCodeAuthenticationToken(google, exchange, accessToken);
219+
220+
OAuth2LoginWithCustomBeansConfig config = this.spring.getContext().getBean(OAuth2LoginWithCustomBeansConfig.class);
221+
222+
ServerAuthenticationConverter converter = config.authenticationConverter;
223+
when(converter.convert(any())).thenReturn(Mono.just(authenticationToken));
224+
225+
ReactiveOAuth2AccessTokenResponseClient<OAuth2AuthorizationCodeGrantRequest> tokenResponseClient = config.tokenResponseClient;
226+
OAuth2Error oauth2Error = new OAuth2Error("invalid_request", "Invalid request", null);
227+
when(tokenResponseClient.getTokenResponse(any())).thenThrow(new OAuth2AuthenticationException(oauth2Error));
228+
229+
webTestClient.get()
230+
.uri("/login/oauth2/code/google")
231+
.exchange()
232+
.expectStatus()
233+
.is3xxRedirection()
234+
.expectHeader()
235+
.valueEquals("Location", "/login?error");
236+
}
237+
238+
@Configuration
239+
static class OAuth2LoginWithCustomBeansConfig {
240+
241+
ServerAuthenticationConverter authenticationConverter = mock(ServerAuthenticationConverter.class);
242+
243+
ReactiveOAuth2AccessTokenResponseClient<OAuth2AuthorizationCodeGrantRequest> tokenResponseClient =
244+
mock(ReactiveOAuth2AccessTokenResponseClient.class);
245+
246+
ReactiveOAuth2UserService<OidcUserRequest, OidcUser> userService = mock(ReactiveOAuth2UserService.class);
247+
248+
@Bean
249+
public SecurityWebFilterChain springSecurityFilter(ServerHttpSecurity http) {
250+
// @formatter:off
251+
http
252+
.authorizeExchange()
253+
.anyExchange().authenticated()
254+
.and()
255+
.oauth2Login()
256+
.authenticationConverter(authenticationConverter)
257+
.authenticationManager(authenticationManager());
258+
return http.build();
259+
// @formatter:on
260+
}
261+
262+
private ReactiveAuthenticationManager authenticationManager() {
263+
OidcAuthorizationCodeReactiveAuthenticationManager oidc =
264+
new OidcAuthorizationCodeReactiveAuthenticationManager(tokenResponseClient, userService);
265+
return oidc;
266+
}
267+
268+
@Bean
269+
public ReactiveOAuth2AccessTokenResponseClient<OAuth2AuthorizationCodeGrantRequest> accessTokenResponseClient() {
270+
return tokenResponseClient;
271+
}
272+
}
273+
185274
static class GitHubWebFilter implements WebFilter {
186275

187276
@Override

0 commit comments

Comments
 (0)