Skip to content

Commit 11c1236

Browse files
committed
OAuth2AuthorizationCodeGrantWebFilter should handle OAuth2AuthorizationException
Fixes gh-8609
1 parent 9846775 commit 11c1236

File tree

6 files changed

+106
-59
lines changed

6 files changed

+106
-59
lines changed

oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/authentication/OAuth2AuthorizationCodeAuthenticationProvider.java

+17-2
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,11 @@
2020
import org.springframework.security.core.AuthenticationException;
2121
import org.springframework.security.oauth2.client.endpoint.OAuth2AccessTokenResponseClient;
2222
import org.springframework.security.oauth2.client.endpoint.OAuth2AuthorizationCodeGrantRequest;
23+
import org.springframework.security.oauth2.core.OAuth2AuthorizationException;
24+
import org.springframework.security.oauth2.core.OAuth2Error;
2325
import org.springframework.security.oauth2.core.endpoint.OAuth2AccessTokenResponse;
26+
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest;
27+
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationResponse;
2428
import org.springframework.util.Assert;
2529

2630
/**
@@ -40,6 +44,7 @@
4044
* @see <a target="_blank" href="https://tools.ietf.org/html/rfc6749#section-4.1.4">Section 4.1.4 Access Token Response</a>
4145
*/
4246
public class OAuth2AuthorizationCodeAuthenticationProvider implements AuthenticationProvider {
47+
private static final String INVALID_STATE_PARAMETER_ERROR_CODE = "invalid_state_parameter";
4348
private final OAuth2AccessTokenResponseClient<OAuth2AuthorizationCodeGrantRequest> accessTokenResponseClient;
4449

4550
/**
@@ -59,8 +64,18 @@ public Authentication authenticate(Authentication authentication) throws Authent
5964
OAuth2AuthorizationCodeAuthenticationToken authorizationCodeAuthentication =
6065
(OAuth2AuthorizationCodeAuthenticationToken) authentication;
6166

62-
OAuth2AuthorizationExchangeValidator.validate(
63-
authorizationCodeAuthentication.getAuthorizationExchange());
67+
OAuth2AuthorizationResponse authorizationResponse = authorizationCodeAuthentication
68+
.getAuthorizationExchange().getAuthorizationResponse();
69+
if (authorizationResponse.statusError()) {
70+
throw new OAuth2AuthorizationException(authorizationResponse.getError());
71+
}
72+
73+
OAuth2AuthorizationRequest authorizationRequest = authorizationCodeAuthentication
74+
.getAuthorizationExchange().getAuthorizationRequest();
75+
if (!authorizationResponse.getState().equals(authorizationRequest.getState())) {
76+
OAuth2Error oauth2Error = new OAuth2Error(INVALID_STATE_PARAMETER_ERROR_CODE);
77+
throw new OAuth2AuthorizationException(oauth2Error);
78+
}
6479

6580
OAuth2AccessTokenResponse accessTokenResponse =
6681
this.accessTokenResponseClient.getTokenResponse(

oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/authentication/OAuth2AuthorizationCodeReactiveAuthenticationManager.java

+17-4
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-2020 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.
@@ -22,9 +22,13 @@
2222
import org.springframework.security.oauth2.client.registration.ClientRegistration;
2323
import org.springframework.security.oauth2.client.userinfo.ReactiveOAuth2UserService;
2424
import org.springframework.security.oauth2.core.OAuth2AccessToken;
25+
import org.springframework.security.oauth2.core.OAuth2AuthorizationException;
26+
import org.springframework.security.oauth2.core.OAuth2Error;
2527
import org.springframework.security.oauth2.core.OAuth2RefreshToken;
2628
import org.springframework.security.oauth2.core.endpoint.OAuth2AccessTokenResponse;
2729
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationExchange;
30+
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest;
31+
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationResponse;
2832
import org.springframework.security.oauth2.core.user.OAuth2User;
2933
import org.springframework.util.Assert;
3034
import reactor.core.publisher.Mono;
@@ -55,8 +59,8 @@
5559
* @see <a target="_blank" href="https://tools.ietf.org/html/rfc6749#section-4.1.3">Section 4.1.3 Access Token Request</a>
5660
* @see <a target="_blank" href="https://tools.ietf.org/html/rfc6749#section-4.1.4">Section 4.1.4 Access Token Response</a>
5761
*/
58-
public class OAuth2AuthorizationCodeReactiveAuthenticationManager implements
59-
ReactiveAuthenticationManager {
62+
public class OAuth2AuthorizationCodeReactiveAuthenticationManager implements ReactiveAuthenticationManager {
63+
private static final String INVALID_STATE_PARAMETER_ERROR_CODE = "invalid_state_parameter";
6064
private final ReactiveOAuth2AccessTokenResponseClient<OAuth2AuthorizationCodeGrantRequest> accessTokenResponseClient;
6165

6266
public OAuth2AuthorizationCodeReactiveAuthenticationManager(
@@ -70,7 +74,16 @@ public Mono<Authentication> authenticate(Authentication authentication) {
7074
return Mono.defer(() -> {
7175
OAuth2AuthorizationCodeAuthenticationToken token = (OAuth2AuthorizationCodeAuthenticationToken) authentication;
7276

73-
OAuth2AuthorizationExchangeValidator.validate(token.getAuthorizationExchange());
77+
OAuth2AuthorizationResponse authorizationResponse = token.getAuthorizationExchange().getAuthorizationResponse();
78+
if (authorizationResponse.statusError()) {
79+
return Mono.error(new OAuth2AuthorizationException(authorizationResponse.getError()));
80+
}
81+
82+
OAuth2AuthorizationRequest authorizationRequest = token.getAuthorizationExchange().getAuthorizationRequest();
83+
if (!authorizationResponse.getState().equals(authorizationRequest.getState())) {
84+
OAuth2Error oauth2Error = new OAuth2Error(INVALID_STATE_PARAMETER_ERROR_CODE);
85+
return Mono.error(new OAuth2AuthorizationException(oauth2Error));
86+
}
7487

7588
OAuth2AuthorizationCodeGrantRequest authzRequest = new OAuth2AuthorizationCodeGrantRequest(
7689
token.getClientRegistration(),

oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/authentication/OAuth2AuthorizationExchangeValidator.java

-47
This file was deleted.

oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/web/server/OAuth2AuthorizationCodeGrantWebFilter.java

+12-4
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@
2727
import org.springframework.security.oauth2.client.authentication.OAuth2AuthorizationCodeAuthenticationToken;
2828
import org.springframework.security.oauth2.client.registration.ReactiveClientRegistrationRepository;
2929
import org.springframework.security.oauth2.client.web.AuthorizationRequestRepository;
30+
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
31+
import org.springframework.security.oauth2.core.OAuth2AuthorizationException;
3032
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest;
3133
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationResponse;
3234
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
@@ -173,15 +175,21 @@ private void updateDefaultAuthenticationConverter() {
173175
public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
174176
return this.requiresAuthenticationMatcher.matches(exchange)
175177
.filter(ServerWebExchangeMatcher.MatchResult::isMatch)
176-
.flatMap(matchResult -> this.authenticationConverter.convert(exchange))
178+
.flatMap(matchResult ->
179+
this.authenticationConverter.convert(exchange)
180+
.onErrorMap(OAuth2AuthorizationException.class, e -> new OAuth2AuthenticationException(
181+
e.getError(), e.getError().toString())))
177182
.switchIfEmpty(chain.filter(exchange).then(Mono.empty()))
178-
.flatMap(token -> authenticate(exchange, chain, token));
183+
.flatMap(token -> authenticate(exchange, chain, token))
184+
.onErrorResume(AuthenticationException.class, e -> this.authenticationFailureHandler
185+
.onAuthenticationFailure(new WebFilterExchange(exchange, chain), e));
179186
}
180187

181-
private Mono<Void> authenticate(ServerWebExchange exchange,
182-
WebFilterChain chain, Authentication token) {
188+
private Mono<Void> authenticate(ServerWebExchange exchange, WebFilterChain chain, Authentication token) {
183189
WebFilterExchange webFilterExchange = new WebFilterExchange(exchange, chain);
184190
return this.authenticationManager.authenticate(token)
191+
.onErrorMap(OAuth2AuthorizationException.class, e -> new OAuth2AuthenticationException(
192+
e.getError(), e.getError().toString()))
185193
.switchIfEmpty(Mono.defer(() -> Mono.error(new IllegalStateException("No provider found for " + token.getClass()))))
186194
.flatMap(authentication -> onAuthenticationSuccess(authentication, webFilterExchange))
187195
.onErrorResume(AuthenticationException.class, e -> this.authenticationFailureHandler

oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/web/server/ServerOAuth2AuthorizationCodeAuthenticationTokenConverter.java

+1-2
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@
1818

1919
import org.springframework.security.core.Authentication;
2020
import org.springframework.security.oauth2.client.authentication.OAuth2AuthorizationCodeAuthenticationToken;
21-
import org.springframework.security.oauth2.client.authentication.OAuth2LoginAuthenticationToken;
2221
import org.springframework.security.oauth2.client.registration.ReactiveClientRegistrationRepository;
2322
import org.springframework.security.oauth2.core.OAuth2AuthorizationException;
2423
import org.springframework.security.oauth2.core.OAuth2Error;
@@ -33,7 +32,7 @@
3332
import reactor.core.publisher.Mono;
3433

3534
/**
36-
* Converts from a {@link ServerWebExchange} to an {@link OAuth2LoginAuthenticationToken} that can be authenticated. The
35+
* Converts from a {@link ServerWebExchange} to an {@link OAuth2AuthorizationCodeAuthenticationToken} that can be authenticated. The
3736
* converter does not validate any errors it only performs a conversion.
3837
* @author Rob Winch
3938
* @since 5.1

oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/web/server/OAuth2AuthorizationCodeGrantWebFilterTests.java

+59
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,9 @@
2929
import org.springframework.security.oauth2.client.registration.ClientRegistration;
3030
import org.springframework.security.oauth2.client.registration.ReactiveClientRegistrationRepository;
3131
import org.springframework.security.oauth2.client.registration.TestClientRegistrations;
32+
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
33+
import org.springframework.security.oauth2.core.OAuth2AuthorizationException;
34+
import org.springframework.security.oauth2.core.OAuth2Error;
3235
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest;
3336
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
3437
import org.springframework.util.CollectionUtils;
@@ -41,6 +44,7 @@
4144
import java.util.Map;
4245

4346
import static org.assertj.core.api.Assertions.assertThatCode;
47+
import static org.assertj.core.api.Assertions.assertThatThrownBy;
4448
import static org.mockito.ArgumentMatchers.any;
4549
import static org.mockito.Mockito.times;
4650
import static org.mockito.Mockito.verify;
@@ -234,6 +238,61 @@ public void filterWhenAuthorizationRequestRedirectUriParametersNotMatchThenNotPr
234238
verifyZeroInteractions(this.authenticationManager);
235239
}
236240

241+
// gh-8609
242+
@Test
243+
public void filterWhenAuthenticationConverterThrowsOAuth2AuthorizationExceptionThenMappedToOAuth2AuthenticationException() {
244+
ClientRegistration clientRegistration = TestClientRegistrations.clientRegistration().build();
245+
when(this.clientRegistrationRepository.findByRegistrationId(any())).thenReturn(Mono.empty());
246+
247+
MockServerHttpRequest authorizationRequest =
248+
createAuthorizationRequest("/authorization/callback");
249+
OAuth2AuthorizationRequest oauth2AuthorizationRequest =
250+
createOAuth2AuthorizationRequest(authorizationRequest, clientRegistration);
251+
when(this.authorizationRequestRepository.loadAuthorizationRequest(any()))
252+
.thenReturn(Mono.just(oauth2AuthorizationRequest));
253+
when(this.authorizationRequestRepository.removeAuthorizationRequest(any()))
254+
.thenReturn(Mono.just(oauth2AuthorizationRequest));
255+
256+
MockServerHttpRequest authorizationResponse = createAuthorizationResponse(authorizationRequest);
257+
MockServerWebExchange exchange = MockServerWebExchange.from(authorizationResponse);
258+
DefaultWebFilterChain chain = new DefaultWebFilterChain(
259+
e -> e.getResponse().setComplete(), Collections.emptyList());
260+
261+
assertThatThrownBy(() -> this.filter.filter(exchange, chain).block())
262+
.isInstanceOf(OAuth2AuthenticationException.class)
263+
.hasMessageContaining("client_registration_not_found");
264+
verifyZeroInteractions(this.authenticationManager);
265+
}
266+
267+
// gh-8609
268+
@Test
269+
public void filterWhenAuthenticationManagerThrowsOAuth2AuthorizationExceptionThenMappedToOAuth2AuthenticationException() {
270+
ClientRegistration clientRegistration = TestClientRegistrations.clientRegistration().build();
271+
when(this.clientRegistrationRepository.findByRegistrationId(any()))
272+
.thenReturn(Mono.just(clientRegistration));
273+
274+
MockServerHttpRequest authorizationRequest =
275+
createAuthorizationRequest("/authorization/callback");
276+
OAuth2AuthorizationRequest oauth2AuthorizationRequest =
277+
createOAuth2AuthorizationRequest(authorizationRequest, clientRegistration);
278+
when(this.authorizationRequestRepository.loadAuthorizationRequest(any()))
279+
.thenReturn(Mono.just(oauth2AuthorizationRequest));
280+
when(this.authorizationRequestRepository.removeAuthorizationRequest(any()))
281+
.thenReturn(Mono.just(oauth2AuthorizationRequest));
282+
283+
when(this.authenticationManager.authenticate(any()))
284+
.thenReturn(Mono.error(new OAuth2AuthorizationException(new OAuth2Error("authorization_error"))));
285+
286+
MockServerHttpRequest authorizationResponse = createAuthorizationResponse(authorizationRequest);
287+
MockServerWebExchange exchange = MockServerWebExchange.from(authorizationResponse);
288+
DefaultWebFilterChain chain = new DefaultWebFilterChain(
289+
e -> e.getResponse().setComplete(), Collections.emptyList());
290+
291+
assertThatThrownBy(() -> this.filter.filter(exchange, chain).block())
292+
.isInstanceOf(OAuth2AuthenticationException.class)
293+
.hasMessageContaining("authorization_error");
294+
}
295+
237296
private static OAuth2AuthorizationRequest createOAuth2AuthorizationRequest(
238297
MockServerHttpRequest authorizationRequest, ClientRegistration registration) {
239298
Map<String, Object> attributes = new HashMap<>();

0 commit comments

Comments
 (0)