Skip to content

Commit 438d291

Browse files
committed
OAuth2AuthorizedClientResolver
Extract out a private API for shared code between the argument resolver and WebClient support. This makes it easier to make changes in both locations. Later we will extract this out so it is not a copy/paste effort. Issue: gh-4921
1 parent 23726ab commit 438d291

File tree

7 files changed

+457
-227
lines changed

7 files changed

+457
-227
lines changed

config/src/main/java/org/springframework/security/config/annotation/web/reactive/ReactiveOAuth2ClientImportSelector.java

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
import org.springframework.context.annotation.ImportSelector;
2222
import org.springframework.core.type.AnnotationMetadata;
2323
import org.springframework.security.oauth2.client.ReactiveOAuth2AuthorizedClientService;
24+
import org.springframework.security.oauth2.client.registration.ReactiveClientRegistrationRepository;
2425
import org.springframework.security.oauth2.client.web.server.AuthenticatedPrincipalServerOAuth2AuthorizedClientRepository;
2526
import org.springframework.security.oauth2.client.web.server.ServerOAuth2AuthorizedClientRepository;
2627
import org.springframework.security.oauth2.client.web.reactive.result.method.annotation.OAuth2AuthorizedClientArgumentResolver;
@@ -53,17 +54,25 @@ public String[] selectImports(AnnotationMetadata importingClassMetadata) {
5354

5455
@Configuration
5556
static class OAuth2ClientWebFluxSecurityConfiguration implements WebFluxConfigurer {
57+
private ReactiveClientRegistrationRepository clientRegistrationRepository;
58+
5659
private ServerOAuth2AuthorizedClientRepository authorizedClientRepository;
5760

5861
private ReactiveOAuth2AuthorizedClientService authorizedClientService;
5962

6063
@Override
6164
public void configureArgumentResolvers(ArgumentResolverConfigurer configurer) {
62-
if (this.authorizedClientRepository != null) {
63-
configurer.addCustomResolver(new OAuth2AuthorizedClientArgumentResolver(getAuthorizedClientRepository()));
65+
if (this.authorizedClientRepository != null && this.clientRegistrationRepository != null) {
66+
configurer.addCustomResolver(new OAuth2AuthorizedClientArgumentResolver(this.clientRegistrationRepository, getAuthorizedClientRepository()));
6467
}
6568
}
6669

70+
@Autowired(required = false)
71+
public void setClientRegistrationRepository(
72+
ReactiveClientRegistrationRepository clientRegistrationRepository) {
73+
this.clientRegistrationRepository = clientRegistrationRepository;
74+
}
75+
6776
@Autowired(required = false)
6877
public void setAuthorizedClientRepository(ServerOAuth2AuthorizedClientRepository authorizedClientRepository) {
6978
this.authorizedClientRepository = authorizedClientRepository;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,185 @@
1+
/*
2+
* Copyright 2002-2018 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.security.oauth2.client.web.reactive.function.client;
18+
19+
import org.springframework.security.authentication.AnonymousAuthenticationToken;
20+
import org.springframework.security.core.Authentication;
21+
import org.springframework.security.core.authority.AuthorityUtils;
22+
import org.springframework.security.core.context.ReactiveSecurityContextHolder;
23+
import org.springframework.security.core.context.SecurityContext;
24+
import org.springframework.security.oauth2.client.ClientAuthorizationRequiredException;
25+
import org.springframework.security.oauth2.client.OAuth2AuthorizedClient;
26+
import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken;
27+
import org.springframework.security.oauth2.client.endpoint.OAuth2ClientCredentialsGrantRequest;
28+
import org.springframework.security.oauth2.client.endpoint.ReactiveOAuth2AccessTokenResponseClient;
29+
import org.springframework.security.oauth2.client.endpoint.WebClientReactiveClientCredentialsTokenResponseClient;
30+
import org.springframework.security.oauth2.client.registration.ClientRegistration;
31+
import org.springframework.security.oauth2.client.registration.ReactiveClientRegistrationRepository;
32+
import org.springframework.security.oauth2.client.web.server.ServerOAuth2AuthorizedClientRepository;
33+
import org.springframework.security.oauth2.core.AuthorizationGrantType;
34+
import org.springframework.security.oauth2.core.endpoint.OAuth2AccessTokenResponse;
35+
import org.springframework.util.Assert;
36+
import org.springframework.web.server.ServerWebExchange;
37+
import reactor.core.publisher.Mono;
38+
39+
import java.util.Optional;
40+
41+
/**
42+
* @author Rob Winch
43+
* @since 5.1
44+
*/
45+
class OAuth2AuthorizedClientResolver {
46+
47+
private static final AnonymousAuthenticationToken ANONYMOUS_USER_TOKEN = new AnonymousAuthenticationToken("anonymous", "anonymousUser",
48+
AuthorityUtils.createAuthorityList("ROLE_USER"));
49+
50+
private final ReactiveClientRegistrationRepository clientRegistrationRepository;
51+
52+
private final ServerOAuth2AuthorizedClientRepository authorizedClientRepository;
53+
54+
private ReactiveOAuth2AccessTokenResponseClient<OAuth2ClientCredentialsGrantRequest> clientCredentialsTokenResponseClient = new WebClientReactiveClientCredentialsTokenResponseClient();
55+
56+
private boolean defaultOAuth2AuthorizedClient;
57+
58+
public OAuth2AuthorizedClientResolver(
59+
ReactiveClientRegistrationRepository clientRegistrationRepository,
60+
ServerOAuth2AuthorizedClientRepository authorizedClientRepository) {
61+
Assert.notNull(clientRegistrationRepository, "clientRegistrationRepository cannot be null");
62+
Assert.notNull(authorizedClientRepository, "authorizedClientRepository cannot be null");
63+
this.clientRegistrationRepository = clientRegistrationRepository;
64+
this.authorizedClientRepository = authorizedClientRepository;
65+
}
66+
67+
/**
68+
* If true, a default {@link OAuth2AuthorizedClient} can be discovered from the current Authentication. It is
69+
* recommended to be cautious with this feature since all HTTP requests will receive the access token if it can be
70+
* resolved from the current Authentication.
71+
* @param defaultOAuth2AuthorizedClient true if a default {@link OAuth2AuthorizedClient} should be used, else false.
72+
* Default is false.
73+
*/
74+
public void setDefaultOAuth2AuthorizedClient(boolean defaultOAuth2AuthorizedClient) {
75+
this.defaultOAuth2AuthorizedClient = defaultOAuth2AuthorizedClient;
76+
}
77+
78+
/**
79+
* Sets the {@link ReactiveOAuth2AccessTokenResponseClient} to be used for getting an {@link OAuth2AuthorizedClient} for
80+
* client_credentials grant.
81+
* @param clientCredentialsTokenResponseClient the client to use
82+
*/
83+
public void setClientCredentialsTokenResponseClient(
84+
ReactiveOAuth2AccessTokenResponseClient<OAuth2ClientCredentialsGrantRequest> clientCredentialsTokenResponseClient) {
85+
Assert.notNull(clientCredentialsTokenResponseClient, "clientCredentialsTokenResponseClient cannot be null");
86+
this.clientCredentialsTokenResponseClient = clientCredentialsTokenResponseClient;
87+
}
88+
89+
Mono<Request> createDefaultedRequest(String clientRegistrationId,
90+
Authentication authentication, ServerWebExchange exchange) {
91+
Mono<Authentication> defaultedAuthentication = Mono.justOrEmpty(authentication)
92+
.switchIfEmpty(currentAuthentication());
93+
94+
Mono<String> defaultedRegistrationId = Mono.justOrEmpty(clientRegistrationId)
95+
.switchIfEmpty(clientRegistrationId(defaultedAuthentication));
96+
97+
Mono<Optional<ServerWebExchange>> defaultedExchange = Mono.justOrEmpty(exchange)
98+
.switchIfEmpty(currentServerWebExchange()).map(Optional::of)
99+
.defaultIfEmpty(Optional.empty());
100+
101+
return Mono.zip(defaultedRegistrationId, defaultedAuthentication, defaultedExchange)
102+
.map(t3 -> new Request(t3.getT1(), t3.getT2(), t3.getT3().orElse(null)));
103+
}
104+
105+
Mono<OAuth2AuthorizedClient> loadAuthorizedClient(Request request) {
106+
String clientRegistrationId = request.getClientRegistrationId();
107+
Authentication authentication = request.getAuthentication();
108+
ServerWebExchange exchange = request.getExchange();
109+
return this.authorizedClientRepository.loadAuthorizedClient(clientRegistrationId, authentication, exchange)
110+
.switchIfEmpty(authorizedClientNotLoaded(clientRegistrationId, authentication, exchange));
111+
}
112+
113+
private Mono<OAuth2AuthorizedClient> authorizedClientNotLoaded(String clientRegistrationId, Authentication authentication, ServerWebExchange exchange) {
114+
return this.clientRegistrationRepository.findByRegistrationId(clientRegistrationId)
115+
.switchIfEmpty(Mono.error(() -> new IllegalArgumentException("Client Registration with id " + clientRegistrationId + " was not found")))
116+
.flatMap(clientRegistration -> {
117+
if (AuthorizationGrantType.CLIENT_CREDENTIALS.equals(clientRegistration.getAuthorizationGrantType())) {
118+
return clientCredentials(clientRegistration, authentication, exchange);
119+
}
120+
return Mono.error(() -> new ClientAuthorizationRequiredException(clientRegistrationId));
121+
});
122+
}
123+
124+
private Mono<? extends OAuth2AuthorizedClient> clientCredentials(
125+
ClientRegistration clientRegistration, Authentication authentication, ServerWebExchange exchange) {
126+
OAuth2ClientCredentialsGrantRequest grantRequest = new OAuth2ClientCredentialsGrantRequest(clientRegistration);
127+
return this.clientCredentialsTokenResponseClient.getTokenResponse(grantRequest)
128+
.flatMap(tokenResponse -> clientCredentialsResponse(clientRegistration, authentication, exchange, tokenResponse));
129+
}
130+
131+
private Mono<OAuth2AuthorizedClient> clientCredentialsResponse(ClientRegistration clientRegistration, Authentication authentication, ServerWebExchange exchange, OAuth2AccessTokenResponse tokenResponse) {
132+
OAuth2AuthorizedClient authorizedClient = new OAuth2AuthorizedClient(
133+
clientRegistration, authentication.getName(), tokenResponse.getAccessToken());
134+
return this.authorizedClientRepository.saveAuthorizedClient(authorizedClient, authentication, exchange)
135+
.thenReturn(authorizedClient);
136+
}
137+
138+
/**
139+
* Attempts to load the client registration id from the current {@link Authentication}
140+
* @return
141+
*/
142+
private Mono<String> clientRegistrationId(Mono<Authentication> authentication) {
143+
return authentication
144+
.filter(t -> this.defaultOAuth2AuthorizedClient && t instanceof OAuth2AuthenticationToken)
145+
.cast(OAuth2AuthenticationToken.class)
146+
.map(OAuth2AuthenticationToken::getAuthorizedClientRegistrationId);
147+
}
148+
149+
private Mono<Authentication> currentAuthentication() {
150+
return ReactiveSecurityContextHolder.getContext()
151+
.map(SecurityContext::getAuthentication)
152+
.defaultIfEmpty(ANONYMOUS_USER_TOKEN);
153+
}
154+
155+
private Mono<ServerWebExchange> currentServerWebExchange() {
156+
return Mono.subscriberContext()
157+
.filter(c -> c.hasKey(ServerWebExchange.class))
158+
.map(c -> c.get(ServerWebExchange.class));
159+
}
160+
161+
static class Request {
162+
private final String clientRegistrationId;
163+
private final Authentication authentication;
164+
private final ServerWebExchange exchange;
165+
166+
public Request(String clientRegistrationId, Authentication authentication,
167+
ServerWebExchange exchange) {
168+
this.clientRegistrationId = clientRegistrationId;
169+
this.authentication = authentication;
170+
this.exchange = exchange;
171+
}
172+
173+
public String getClientRegistrationId() {
174+
return this.clientRegistrationId;
175+
}
176+
177+
public Authentication getAuthentication() {
178+
return this.authentication;
179+
}
180+
181+
public ServerWebExchange getExchange() {
182+
return this.exchange;
183+
}
184+
}
185+
}

0 commit comments

Comments
 (0)