Skip to content

Commit 1749c8d

Browse files
committed
OAuth2AuthorizationCodeGrantWebFilter matches on registered redirect-uri
Fixes gh-7036
1 parent a5391b6 commit 1749c8d

File tree

3 files changed

+105
-26
lines changed

3 files changed

+105
-26
lines changed

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

+38-8
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,11 +34,17 @@
3434
import org.springframework.security.oauth2.client.registration.InMemoryReactiveClientRegistrationRepository;
3535
import org.springframework.security.oauth2.client.registration.ReactiveClientRegistrationRepository;
3636
import org.springframework.security.oauth2.client.registration.TestClientRegistrations;
37+
import org.springframework.security.oauth2.client.web.server.ServerAuthorizationRequestRepository;
3738
import org.springframework.security.oauth2.client.web.server.ServerOAuth2AuthorizedClientRepository;
39+
import org.springframework.security.oauth2.client.web.server.WebSessionOAuth2ServerAuthorizationRequestRepository;
3840
import org.springframework.security.oauth2.core.OAuth2AccessToken;
3941
import org.springframework.security.oauth2.core.TestOAuth2AccessTokens;
4042
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationExchange;
41-
import org.springframework.security.oauth2.core.endpoint.TestOAuth2AuthorizationExchanges;
43+
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest;
44+
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationResponse;
45+
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
46+
import org.springframework.security.oauth2.core.endpoint.TestOAuth2AuthorizationRequests;
47+
import org.springframework.security.oauth2.core.endpoint.TestOAuth2AuthorizationResponses;
4248
import org.springframework.security.test.context.annotation.SecurityTestExecutionListeners;
4349
import org.springframework.security.test.context.support.WithMockUser;
4450
import org.springframework.security.web.server.SecurityWebFilterChain;
@@ -69,8 +75,11 @@ public class OAuth2ClientSpecTests {
6975

7076
private ClientRegistration registration = TestClientRegistrations.clientRegistration().build();
7177

78+
private ApplicationContext context;
79+
7280
@Autowired
7381
public void setApplicationContext(ApplicationContext context) {
82+
this.context = context;
7483
this.client = WebTestClient.bindToApplicationContext(context).build();
7584
}
7685

@@ -140,19 +149,40 @@ public void oauth2ClientWhenCustomObjectsThenUsed() {
140149

141150
ServerAuthenticationConverter converter = config.authenticationConverter;
142151
ReactiveAuthenticationManager manager = config.manager;
152+
ServerAuthorizationRequestRepository<OAuth2AuthorizationRequest> authorizationRequestRepository =
153+
new WebSessionOAuth2ServerAuthorizationRequestRepository();
143154

144-
OAuth2AuthorizationExchange exchange = TestOAuth2AuthorizationExchanges.success();
155+
OAuth2AuthorizationRequest authorizationRequest = TestOAuth2AuthorizationRequests.request()
156+
.redirectUri("/authorize/oauth2/code/registration-id")
157+
.build();
158+
OAuth2AuthorizationResponse authorizationResponse = TestOAuth2AuthorizationResponses.success()
159+
.redirectUri("/authorize/oauth2/code/registration-id")
160+
.build();
161+
OAuth2AuthorizationExchange authorizationExchange =
162+
new OAuth2AuthorizationExchange(authorizationRequest, authorizationResponse);
145163
OAuth2AccessToken accessToken = TestOAuth2AccessTokens.noScopes();
146164

147-
OAuth2AuthorizationCodeAuthenticationToken result = new OAuth2AuthorizationCodeAuthenticationToken(this.registration, exchange, accessToken);
165+
OAuth2AuthorizationCodeAuthenticationToken result = new OAuth2AuthorizationCodeAuthenticationToken(
166+
this.registration, authorizationExchange, accessToken);
148167

149168
when(converter.convert(any())).thenReturn(Mono.just(new TestingAuthenticationToken("a", "b", "c")));
150169
when(manager.authenticate(any())).thenReturn(Mono.just(result));
151170

152-
this.client.get()
153-
.uri("/authorize/oauth2/code/registration-id")
154-
.exchange()
155-
.expectStatus().is3xxRedirection();
171+
WebTestClient client = WebTestClient.bindToApplicationContext(this.context)
172+
.webFilter((exchange, chain) ->
173+
authorizationRequestRepository.saveAuthorizationRequest(authorizationRequest, exchange)
174+
.then(chain.filter(exchange).then(Mono.empty()))
175+
)
176+
.build();
177+
178+
client.get()
179+
.uri(uriBuilder ->
180+
uriBuilder.path("/authorize/oauth2/code/registration-id")
181+
.queryParam(OAuth2ParameterNames.CODE, "code")
182+
.queryParam(OAuth2ParameterNames.STATE, "state")
183+
.build())
184+
.exchange()
185+
.expectStatus().is3xxRedirection();
156186

157187
verify(converter).convert(any());
158188
verify(manager).authenticate(any());

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

+27-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-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.
@@ -35,12 +35,13 @@
3535
import org.springframework.security.web.server.authentication.ServerAuthenticationConverter;
3636
import org.springframework.security.web.server.authentication.ServerAuthenticationFailureHandler;
3737
import org.springframework.security.web.server.authentication.ServerAuthenticationSuccessHandler;
38-
import org.springframework.security.web.server.util.matcher.PathPatternParserServerWebExchangeMatcher;
3938
import org.springframework.security.web.server.util.matcher.ServerWebExchangeMatcher;
4039
import org.springframework.util.Assert;
40+
import org.springframework.util.MultiValueMap;
4141
import org.springframework.web.server.ServerWebExchange;
4242
import org.springframework.web.server.WebFilter;
4343
import org.springframework.web.server.WebFilterChain;
44+
import org.springframework.web.util.UriComponentsBuilder;
4445
import reactor.core.publisher.Mono;
4546

4647
/**
@@ -71,6 +72,7 @@
7172
* </ul>
7273
*
7374
* @author Rob Winch
75+
* @author Joe Grandja
7476
* @since 5.1
7577
* @see OAuth2AuthorizationCodeAuthenticationToken
7678
* @see org.springframework.security.oauth2.client.authentication.OAuth2AuthorizationCodeReactiveAuthenticationManager
@@ -89,6 +91,9 @@ public class OAuth2AuthorizationCodeGrantWebFilter implements WebFilter {
8991

9092
private final ServerOAuth2AuthorizedClientRepository authorizedClientRepository;
9193

94+
private ServerAuthorizationRequestRepository<OAuth2AuthorizationRequest> authorizationRequestRepository =
95+
new WebSessionOAuth2ServerAuthorizationRequestRepository();
96+
9297
private ServerAuthenticationSuccessHandler authenticationSuccessHandler;
9398

9499
private ServerAuthenticationConverter authenticationConverter;
@@ -109,7 +114,7 @@ public OAuth2AuthorizationCodeGrantWebFilter(
109114
Assert.notNull(authorizedClientRepository, "authorizedClientRepository cannot be null");
110115
this.authenticationManager = authenticationManager;
111116
this.authorizedClientRepository = authorizedClientRepository;
112-
this.requiresAuthenticationMatcher = new PathPatternParserServerWebExchangeMatcher("/{action}/oauth2/code/{registrationId}");
117+
this.requiresAuthenticationMatcher = this::matchesAuthorizationResponse;
113118
this.authenticationConverter = new ServerOAuth2AuthorizationCodeAuthenticationTokenConverter(clientRegistrationRepository);
114119
this.authenticationSuccessHandler = new RedirectServerAuthenticationSuccessHandler();
115120
this.authenticationFailureHandler = (webFilterExchange, exception) -> Mono.error(exception);
@@ -124,7 +129,7 @@ public OAuth2AuthorizationCodeGrantWebFilter(
124129
Assert.notNull(authorizedClientRepository, "authorizedClientRepository cannot be null");
125130
this.authenticationManager = authenticationManager;
126131
this.authorizedClientRepository = authorizedClientRepository;
127-
this.requiresAuthenticationMatcher = new PathPatternParserServerWebExchangeMatcher("/{action}/oauth2/code/{registrationId}");
132+
this.requiresAuthenticationMatcher = this::matchesAuthorizationResponse;
128133
this.authenticationConverter = authenticationConverter;
129134
this.authenticationSuccessHandler = new RedirectServerAuthenticationSuccessHandler();
130135
this.authenticationFailureHandler = (webFilterExchange, exception) -> Mono.error(exception);
@@ -164,4 +169,22 @@ private Mono<Void> onAuthenticationSuccess(Authentication authentication, WebFil
164169
.flatMap(principal -> this.authorizedClientRepository.saveAuthorizedClient(authorizedClient, principal, webFilterExchange.getExchange()))
165170
);
166171
}
172+
173+
private Mono<ServerWebExchangeMatcher.MatchResult> matchesAuthorizationResponse(ServerWebExchange exchange) {
174+
return this.authorizationRequestRepository.loadAuthorizationRequest(exchange)
175+
.flatMap(authorizationRequest -> {
176+
String requestUrl = UriComponentsBuilder.fromUri(exchange.getRequest().getURI())
177+
.query(null)
178+
.build()
179+
.toUriString();
180+
MultiValueMap<String, String> queryParams = exchange.getRequest().getQueryParams();
181+
if (requestUrl.equals(authorizationRequest.getRedirectUri()) &&
182+
OAuth2AuthorizationResponseUtils.isAuthorizationResponse(queryParams)) {
183+
return ServerWebExchangeMatcher.MatchResult.match();
184+
}
185+
return ServerWebExchangeMatcher.MatchResult.notMatch();
186+
})
187+
.filter(ServerWebExchangeMatcher.MatchResult::isMatch)
188+
.switchIfEmpty(ServerWebExchangeMatcher.MatchResult.notMatch());
189+
}
167190
}

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

+40-14
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.
@@ -28,16 +28,22 @@
2828
import org.springframework.security.core.Authentication;
2929
import org.springframework.security.oauth2.client.authentication.OAuth2AuthorizationCodeAuthenticationToken;
3030
import org.springframework.security.oauth2.client.authentication.TestOAuth2AuthorizationCodeAuthenticationTokens;
31+
import org.springframework.security.oauth2.client.registration.ClientRegistration;
3132
import org.springframework.security.oauth2.client.registration.ReactiveClientRegistrationRepository;
33+
import org.springframework.security.oauth2.client.registration.TestClientRegistrations;
34+
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationExchange;
35+
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest;
36+
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationResponse;
37+
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
38+
import org.springframework.security.oauth2.core.endpoint.TestOAuth2AuthorizationRequests;
39+
import org.springframework.security.oauth2.core.endpoint.TestOAuth2AuthorizationResponses;
3240
import org.springframework.security.web.server.authentication.ServerAuthenticationConverter;
3341
import org.springframework.web.server.handler.DefaultWebFilterChain;
3442
import reactor.core.publisher.Mono;
3543

36-
import static org.assertj.core.api.Assertions.*;
44+
import static org.assertj.core.api.Assertions.assertThatCode;
3745
import static org.mockito.ArgumentMatchers.any;
38-
import static org.mockito.Mockito.verify;
39-
import static org.mockito.Mockito.verifyZeroInteractions;
40-
import static org.mockito.Mockito.when;
46+
import static org.mockito.Mockito.*;
4147

4248
/**
4349
* @author Rob Winch
@@ -53,6 +59,9 @@ public class OAuth2AuthorizationCodeGrantWebFilterTests {
5359
@Mock
5460
private ServerOAuth2AuthorizedClientRepository authorizedClientRepository;
5561

62+
private ServerAuthorizationRequestRepository<OAuth2AuthorizationRequest> authorizationRequestRepository =
63+
new WebSessionOAuth2ServerAuthorizationRequestRepository();
64+
5665
@Before
5766
public void setup() {
5867
this.filter = new OAuth2AuthorizationCodeGrantWebFilter(
@@ -101,25 +110,42 @@ public void filterWhenNotMatchThenAuthenticationManagerNotCalled() {
101110

102111
@Test
103112
public void filterWhenMatchThenAuthorizedClientSaved() {
104-
Mono<Authentication> authentication = Mono
105-
.just(TestOAuth2AuthorizationCodeAuthenticationTokens.unauthenticated());
113+
OAuth2AuthorizationRequest authorizationRequest = TestOAuth2AuthorizationRequests.request()
114+
.redirectUri("/authorize/registration-id")
115+
.build();
116+
OAuth2AuthorizationResponse authorizationResponse = TestOAuth2AuthorizationResponses.success()
117+
.redirectUri("/authorize/registration-id")
118+
.build();
119+
OAuth2AuthorizationExchange authorizationExchange =
120+
new OAuth2AuthorizationExchange(authorizationRequest, authorizationResponse);
121+
ClientRegistration registration = TestClientRegistrations.clientRegistration().build();
122+
Mono<Authentication> authentication = Mono.just(
123+
new OAuth2AuthorizationCodeAuthenticationToken(registration, authorizationExchange));
106124
OAuth2AuthorizationCodeAuthenticationToken authenticated = TestOAuth2AuthorizationCodeAuthenticationTokens
107125
.authenticated();
126+
127+
when(this.authenticationManager.authenticate(any())).thenReturn(
128+
Mono.just(authenticated));
129+
when(this.authorizedClientRepository.saveAuthorizedClient(any(), any(), any()))
130+
.thenReturn(Mono.empty());
108131
ServerAuthenticationConverter converter = e -> authentication;
132+
109133
this.filter = new OAuth2AuthorizationCodeGrantWebFilter(
110134
this.authenticationManager, converter, this.authorizedClientRepository);
111-
MockServerWebExchange exchange = MockServerWebExchange.from(MockServerHttpRequest
112-
.get("/authorize/oauth2/code/registration-id"));
135+
136+
MockServerHttpRequest request = MockServerHttpRequest
137+
.get("/authorize/registration-id")
138+
.queryParam(OAuth2ParameterNames.CODE, "code")
139+
.queryParam(OAuth2ParameterNames.STATE, "state")
140+
.build();
141+
MockServerWebExchange exchange = MockServerWebExchange.from(request);
113142
DefaultWebFilterChain chain = new DefaultWebFilterChain(
114143
e -> e.getResponse().setComplete());
115-
when(this.authenticationManager.authenticate(any())).thenReturn(Mono.just(
116-
authenticated));
117-
when(this.authorizedClientRepository.saveAuthorizedClient(any(), any(), any()))
118-
.thenReturn(Mono.empty());
144+
145+
this.authorizationRequestRepository.saveAuthorizationRequest(authorizationRequest, exchange).block();
119146

120147
this.filter.filter(exchange, chain).block();
121148

122149
verify(this.authorizedClientRepository).saveAuthorizedClient(any(), any(AnonymousAuthenticationToken.class), any());
123-
124150
}
125151
}

0 commit comments

Comments
 (0)