Skip to content

Commit 1c43f4c

Browse files
committed
Merge branch 'spring-projectsgh-7569-fix' of github.com:ankurpathak/spring-security into spring-projectsgh-7569
2 parents d102cae + 9c8d027 commit 1c43f4c

File tree

2 files changed

+447
-0
lines changed

2 files changed

+447
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
/*
2+
* Copyright 2002-2019 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+
* https://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+
package org.springframework.security.oauth2.client;
17+
18+
import org.springframework.lang.Nullable;
19+
import org.springframework.security.core.Authentication;
20+
import org.springframework.security.oauth2.client.registration.ReactiveClientRegistrationRepository;
21+
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
22+
import org.springframework.util.Assert;
23+
import org.springframework.util.CollectionUtils;
24+
import org.springframework.util.StringUtils;
25+
import org.springframework.web.server.ServerWebExchange;
26+
import reactor.core.publisher.Mono;
27+
28+
import java.util.Collections;
29+
import java.util.HashMap;
30+
import java.util.Map;
31+
import java.util.Optional;
32+
import java.util.function.Function;
33+
34+
/**
35+
* An implementation of an {@link ReactiveOAuth2AuthorizedClientManager}
36+
* that is capable of operating outside of a {@code ServerHttpRequest} context,
37+
* e.g. in a scheduled/background thread and/or in the service-tier.
38+
*
39+
* @author Ankur Pathak
40+
* @see ReactiveOAuth2AuthorizedClientManager
41+
* @see ReactiveOAuth2AuthorizedClientProvider
42+
* @see ReactiveOAuth2AuthorizedClientService
43+
* @since 5.3
44+
*/
45+
public final class OAuth2AuthorizedClientServiceReactiveOAuth2AuthorizedClientManager implements ReactiveOAuth2AuthorizedClientManager {
46+
private final ReactiveClientRegistrationRepository clientRegistrationRepository;
47+
private final ReactiveOAuth2AuthorizedClientService authorizedClientService;
48+
private ReactiveOAuth2AuthorizedClientProvider authorizedClientProvider = context -> Mono.empty();
49+
private Function<OAuth2AuthorizeRequest, Mono<Map<String, Object>>> contextAttributesMapper = new DefaultContextAttributesMapper();
50+
51+
/**
52+
* Constructs an {@code OAuth2AuthorizedClientServiceReactiveOAuth2AuthorizedClientManager} using the provided parameters.
53+
*
54+
* @param clientRegistrationRepository the repository of client registrations
55+
* @param authorizedClientService the authorized client service
56+
*/
57+
public OAuth2AuthorizedClientServiceReactiveOAuth2AuthorizedClientManager(ReactiveClientRegistrationRepository clientRegistrationRepository,
58+
ReactiveOAuth2AuthorizedClientService authorizedClientService) {
59+
Assert.notNull(clientRegistrationRepository, "clientRegistrationRepository cannot be null");
60+
Assert.notNull(authorizedClientService, "authorizedClientService cannot be null");
61+
this.clientRegistrationRepository = clientRegistrationRepository;
62+
this.authorizedClientService = authorizedClientService;
63+
}
64+
65+
@Nullable
66+
@Override
67+
public Mono<OAuth2AuthorizedClient> authorize(OAuth2AuthorizeRequest authorizeRequest) {
68+
Assert.notNull(authorizeRequest, "authorizeRequest cannot be null");
69+
String clientRegistrationId = authorizeRequest.getClientRegistrationId();
70+
OAuth2AuthorizedClient authorizedClient = authorizeRequest.getAuthorizedClient();
71+
Authentication principal = authorizeRequest.getPrincipal();
72+
// @formatter:off
73+
return Mono.justOrEmpty(authorizedClient)
74+
.map(OAuth2AuthorizationContext::withAuthorizedClient)
75+
.switchIfEmpty(Mono.defer(() -> this.clientRegistrationRepository.findByRegistrationId(clientRegistrationId)
76+
.flatMap(clientRegistration -> this.authorizedClientService.loadAuthorizedClient(clientRegistrationId, principal.getName())
77+
.map(OAuth2AuthorizationContext::withAuthorizedClient)
78+
.switchIfEmpty(Mono.fromSupplier(() -> OAuth2AuthorizationContext.withClientRegistration(clientRegistration)))
79+
)
80+
.switchIfEmpty(Mono.error(new IllegalArgumentException("Could not find ClientRegistration with id '" + clientRegistrationId + "'")))
81+
)
82+
)
83+
.flatMap(contextBuilder -> this.contextAttributesMapper.apply(authorizeRequest)
84+
.filter(contextAttributes-> !CollectionUtils.isEmpty(contextAttributes))
85+
.map(contextAttributes -> contextBuilder.principal(principal)
86+
.attributes(attributes -> {
87+
attributes.putAll(contextAttributes);
88+
}).build())
89+
).flatMap(authorizationContext -> this.authorizedClientProvider.authorize(authorizationContext)
90+
.doOnNext(_authorizedClient -> authorizedClientService.saveAuthorizedClient(_authorizedClient, principal))
91+
.switchIfEmpty(Mono.defer(()-> Mono.justOrEmpty(Optional.ofNullable(authorizationContext.getAuthorizedClient()))))
92+
);
93+
// @formatter:on
94+
}
95+
96+
/**
97+
* Sets the {@link ReactiveOAuth2AuthorizedClientProvider} used for authorizing (or re-authorizing) an OAuth 2.0 Client.
98+
*
99+
* @param authorizedClientProvider the {@link ReactiveOAuth2AuthorizedClientProvider} used for authorizing (or re-authorizing) an OAuth 2.0 Client
100+
*/
101+
public void setAuthorizedClientProvider(ReactiveOAuth2AuthorizedClientProvider authorizedClientProvider) {
102+
Assert.notNull(authorizedClientProvider, "authorizedClientProvider cannot be null");
103+
this.authorizedClientProvider = authorizedClientProvider;
104+
}
105+
106+
/**
107+
* Sets the {@code Function} used for mapping attribute(s) from the {@link OAuth2AuthorizeRequest} to a {@code Map} of attributes
108+
* to be associated to the {@link OAuth2AuthorizationContext#getAttributes() authorization context}.
109+
*
110+
* @param contextAttributesMapper the {@code Function} used for supplying the {@code Map} of attributes
111+
* to the {@link OAuth2AuthorizationContext#getAttributes() authorization context}
112+
*/
113+
public void setContextAttributesMapper(Function<OAuth2AuthorizeRequest, Mono<Map<String, Object>>> contextAttributesMapper) {
114+
Assert.notNull(contextAttributesMapper, "contextAttributesMapper cannot be null");
115+
this.contextAttributesMapper = contextAttributesMapper;
116+
}
117+
118+
private static Mono<ServerWebExchange> currentServerWebExchange() {
119+
return Mono.subscriberContext()
120+
.filter(c -> c.hasKey(ServerWebExchange.class))
121+
.map(c -> c.get(ServerWebExchange.class));
122+
}
123+
124+
/**
125+
* The default implementation of the {@link #setContextAttributesMapper(Function) contextAttributesMapper}.
126+
*/
127+
public static class DefaultContextAttributesMapper implements Function<OAuth2AuthorizeRequest, Mono<Map<String, Object>>> {
128+
129+
@Override
130+
public Mono<Map<String, Object>> apply(OAuth2AuthorizeRequest authorizeRequest) {
131+
ServerWebExchange serverWebExchange = authorizeRequest.getAttribute(ServerWebExchange.class.getName());
132+
return Mono.justOrEmpty(serverWebExchange)
133+
.switchIfEmpty(Mono.defer(() -> currentServerWebExchange()))
134+
.flatMap(exchange -> {
135+
Map<String, Object> contextAttributes = Collections.emptyMap();
136+
String scope = exchange.getRequest().getQueryParams().getFirst(OAuth2ParameterNames.SCOPE);
137+
if (StringUtils.hasText(scope)) {
138+
contextAttributes = new HashMap<>();
139+
contextAttributes.put(OAuth2AuthorizationContext.REQUEST_SCOPE_ATTRIBUTE_NAME,
140+
StringUtils.delimitedListToStringArray(scope, " "));
141+
}
142+
return Mono.just(contextAttributes);
143+
})
144+
.defaultIfEmpty(Collections.emptyMap());
145+
}
146+
}
147+
}

0 commit comments

Comments
 (0)