Skip to content

Commit a604468

Browse files
committed
OAuth2AuthorizeRequest supports attributes
Fixes gh-7341
1 parent 4a754c1 commit a604468

22 files changed

+554
-526
lines changed

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

+2-2
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@
2626
import org.springframework.security.oauth2.client.registration.ReactiveClientRegistrationRepository;
2727
import org.springframework.security.oauth2.client.web.reactive.result.method.annotation.OAuth2AuthorizedClientArgumentResolver;
2828
import org.springframework.security.oauth2.client.web.server.AuthenticatedPrincipalServerOAuth2AuthorizedClientRepository;
29-
import org.springframework.security.oauth2.client.web.server.DefaultServerOAuth2AuthorizedClientManager;
29+
import org.springframework.security.oauth2.client.web.DefaultReactiveOAuth2AuthorizedClientManager;
3030
import org.springframework.security.oauth2.client.web.server.ServerOAuth2AuthorizedClientRepository;
3131
import org.springframework.util.ClassUtils;
3232
import org.springframework.web.reactive.config.WebFluxConfigurer;
@@ -73,7 +73,7 @@ public void configureArgumentResolvers(ArgumentResolverConfigurer configurer) {
7373
.clientCredentials()
7474
.password()
7575
.build();
76-
DefaultServerOAuth2AuthorizedClientManager authorizedClientManager = new DefaultServerOAuth2AuthorizedClientManager(
76+
DefaultReactiveOAuth2AuthorizedClientManager authorizedClientManager = new DefaultReactiveOAuth2AuthorizedClientManager(
7777
this.clientRegistrationRepository, getAuthorizedClientRepository());
7878
authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider);
7979
configurer.addCustomResolver(new OAuth2AuthorizedClientArgumentResolver(authorizedClientManager));
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,198 @@
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.ClientRegistration;
21+
import org.springframework.util.Assert;
22+
import org.springframework.util.CollectionUtils;
23+
24+
import java.util.Collections;
25+
import java.util.HashMap;
26+
import java.util.LinkedHashMap;
27+
import java.util.Map;
28+
import java.util.function.Consumer;
29+
30+
/**
31+
* Represents a request the {@link OAuth2AuthorizedClientManager} uses to
32+
* {@link OAuth2AuthorizedClientManager#authorize(OAuth2AuthorizeRequest) authorize} (or re-authorize)
33+
* the {@link ClientRegistration client} identified by the provided {@link #getClientRegistrationId() clientRegistrationId}.
34+
*
35+
* @author Joe Grandja
36+
* @since 5.2
37+
* @see OAuth2AuthorizedClientManager
38+
*/
39+
public final class OAuth2AuthorizeRequest {
40+
private String clientRegistrationId;
41+
private OAuth2AuthorizedClient authorizedClient;
42+
private Authentication principal;
43+
private Map<String, Object> attributes;
44+
45+
private OAuth2AuthorizeRequest() {
46+
}
47+
48+
/**
49+
* Returns the identifier for the {@link ClientRegistration client registration}.
50+
*
51+
* @return the identifier for the client registration
52+
*/
53+
public String getClientRegistrationId() {
54+
return this.clientRegistrationId;
55+
}
56+
57+
/**
58+
* Returns the {@link OAuth2AuthorizedClient authorized client} or {@code null} if it was not provided.
59+
*
60+
* @return the {@link OAuth2AuthorizedClient} or {@code null} if it was not provided
61+
*/
62+
@Nullable
63+
public OAuth2AuthorizedClient getAuthorizedClient() {
64+
return this.authorizedClient;
65+
}
66+
67+
/**
68+
* Returns the {@code Principal} (to be) associated to the authorized client.
69+
*
70+
* @return the {@code Principal} (to be) associated to the authorized client
71+
*/
72+
public Authentication getPrincipal() {
73+
return this.principal;
74+
}
75+
76+
/**
77+
* Returns the attributes associated to the request.
78+
*
79+
* @return a {@code Map} of the attributes associated to the request
80+
*/
81+
public Map<String, Object> getAttributes() {
82+
return this.attributes;
83+
}
84+
85+
/**
86+
* Returns the value of an attribute associated to the request or {@code null} if not available.
87+
*
88+
* @param name the name of the attribute
89+
* @param <T> the type of the attribute
90+
* @return the value of the attribute associated to the request
91+
*/
92+
@Nullable
93+
@SuppressWarnings("unchecked")
94+
public <T> T getAttribute(String name) {
95+
return (T) this.getAttributes().get(name);
96+
}
97+
98+
/**
99+
* Returns a new {@link Builder} initialized with the identifier for the {@link ClientRegistration client registration}.
100+
*
101+
* @param clientRegistrationId the identifier for the {@link ClientRegistration client registration}
102+
* @return the {@link Builder}
103+
*/
104+
public static Builder withClientRegistrationId(String clientRegistrationId) {
105+
return new Builder(clientRegistrationId);
106+
}
107+
108+
/**
109+
* Returns a new {@link Builder} initialized with the {@link OAuth2AuthorizedClient authorized client}.
110+
*
111+
* @param authorizedClient the {@link OAuth2AuthorizedClient authorized client}
112+
* @return the {@link Builder}
113+
*/
114+
public static Builder withAuthorizedClient(OAuth2AuthorizedClient authorizedClient) {
115+
return new Builder(authorizedClient);
116+
}
117+
118+
/**
119+
* A builder for {@link OAuth2AuthorizeRequest}.
120+
*/
121+
public static class Builder {
122+
private String clientRegistrationId;
123+
private OAuth2AuthorizedClient authorizedClient;
124+
private Authentication principal;
125+
private Map<String, Object> attributes;
126+
127+
private Builder(String clientRegistrationId) {
128+
Assert.hasText(clientRegistrationId, "clientRegistrationId cannot be empty");
129+
this.clientRegistrationId = clientRegistrationId;
130+
}
131+
132+
private Builder(OAuth2AuthorizedClient authorizedClient) {
133+
Assert.notNull(authorizedClient, "authorizedClient cannot be null");
134+
this.authorizedClient = authorizedClient;
135+
}
136+
137+
/**
138+
* Sets the {@code Principal} (to be) associated to the authorized client.
139+
*
140+
* @param principal the {@code Principal} (to be) associated to the authorized client
141+
* @return the {@link Builder}
142+
*/
143+
public Builder principal(Authentication principal) {
144+
this.principal = principal;
145+
return this;
146+
}
147+
148+
/**
149+
* Provides a {@link Consumer} access to the attributes associated to the request.
150+
*
151+
* @param attributesConsumer a {@link Consumer} of the attributes associated to the request
152+
* @return the {@link Builder}
153+
*/
154+
public Builder attributes(Consumer<Map<String, Object>> attributesConsumer) {
155+
if (this.attributes == null) {
156+
this.attributes = new HashMap<>();
157+
}
158+
attributesConsumer.accept(this.attributes);
159+
return this;
160+
}
161+
162+
/**
163+
* Sets an attribute associated to the request.
164+
*
165+
* @param name the name of the attribute
166+
* @param value the value of the attribute
167+
* @return the {@link Builder}
168+
*/
169+
public Builder attribute(String name, Object value) {
170+
if (this.attributes == null) {
171+
this.attributes = new HashMap<>();
172+
}
173+
this.attributes.put(name, value);
174+
return this;
175+
}
176+
177+
/**
178+
* Builds a new {@link OAuth2AuthorizeRequest}.
179+
*
180+
* @return a {@link OAuth2AuthorizeRequest}
181+
*/
182+
public OAuth2AuthorizeRequest build() {
183+
Assert.notNull(this.principal, "principal cannot be null");
184+
OAuth2AuthorizeRequest authorizeRequest = new OAuth2AuthorizeRequest();
185+
if (this.authorizedClient != null) {
186+
authorizeRequest.clientRegistrationId = this.authorizedClient.getClientRegistration().getRegistrationId();
187+
authorizeRequest.authorizedClient = this.authorizedClient;
188+
} else {
189+
authorizeRequest.clientRegistrationId = this.clientRegistrationId;
190+
}
191+
authorizeRequest.principal = this.principal;
192+
authorizeRequest.attributes = Collections.unmodifiableMap(
193+
CollectionUtils.isEmpty(this.attributes) ?
194+
Collections.emptyMap() : new LinkedHashMap<>(this.attributes));
195+
return authorizeRequest;
196+
}
197+
}
198+
}
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,11 @@
1313
* See the License for the specific language governing permissions and
1414
* limitations under the License.
1515
*/
16-
package org.springframework.security.oauth2.client.web;
16+
package org.springframework.security.oauth2.client;
1717

1818
import org.springframework.lang.Nullable;
19-
import org.springframework.security.oauth2.client.OAuth2AuthorizedClient;
20-
import org.springframework.security.oauth2.client.OAuth2AuthorizedClientProvider;
2119
import org.springframework.security.oauth2.client.registration.ClientRegistration;
20+
import org.springframework.security.oauth2.client.web.OAuth2AuthorizedClientRepository;
2221

2322
/**
2423
* Implementations of this interface are responsible for the overall management
@@ -30,13 +29,14 @@
3029
* <li>Authorizing (or re-authorizing) an OAuth 2.0 Client
3130
* by leveraging an {@link OAuth2AuthorizedClientProvider}(s).</li>
3231
* <li>Managing the persistence of an {@link OAuth2AuthorizedClient} between requests,
33-
* typically using an {@link OAuth2AuthorizedClientRepository}.</li>
32+
* typically using an {@link OAuth2AuthorizedClientService} OR {@link OAuth2AuthorizedClientRepository}.</li>
3433
* </ol>
3534
*
3635
* @author Joe Grandja
3736
* @since 5.2
3837
* @see OAuth2AuthorizedClient
3938
* @see OAuth2AuthorizedClientProvider
39+
* @see OAuth2AuthorizedClientService
4040
* @see OAuth2AuthorizedClientRepository
4141
*/
4242
public interface OAuth2AuthorizedClientManager {
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,10 @@
1313
* See the License for the specific language governing permissions and
1414
* limitations under the License.
1515
*/
16-
package org.springframework.security.oauth2.client.web.server;
16+
package org.springframework.security.oauth2.client;
1717

18-
import org.springframework.security.oauth2.client.OAuth2AuthorizedClient;
19-
import org.springframework.security.oauth2.client.ReactiveOAuth2AuthorizedClientProvider;
2018
import org.springframework.security.oauth2.client.registration.ClientRegistration;
19+
import org.springframework.security.oauth2.client.web.server.ServerOAuth2AuthorizedClientRepository;
2120
import reactor.core.publisher.Mono;
2221

2322
/**
@@ -30,33 +29,34 @@
3029
* <li>Authorizing (or re-authorizing) an OAuth 2.0 Client
3130
* by leveraging a {@link ReactiveOAuth2AuthorizedClientProvider}(s).</li>
3231
* <li>Managing the persistence of an {@link OAuth2AuthorizedClient} between requests,
33-
* typically using an {@link ServerOAuth2AuthorizedClientRepository}.</li>
32+
* typically using a {@link ReactiveOAuth2AuthorizedClientService} OR {@link ServerOAuth2AuthorizedClientRepository}.</li>
3433
* </ol>
3534
*
3635
* @author Joe Grandja
3736
* @since 5.2
3837
* @see OAuth2AuthorizedClient
3938
* @see ReactiveOAuth2AuthorizedClientProvider
39+
* @see ReactiveOAuth2AuthorizedClientService
4040
* @see ServerOAuth2AuthorizedClientRepository
4141
*/
42-
public interface ServerOAuth2AuthorizedClientManager {
42+
public interface ReactiveOAuth2AuthorizedClientManager {
4343

4444
/**
4545
* Attempt to authorize or re-authorize (if required) the {@link ClientRegistration client}
46-
* identified by the provided {@link ServerOAuth2AuthorizeRequest#getClientRegistrationId() clientRegistrationId}.
46+
* identified by the provided {@link OAuth2AuthorizeRequest#getClientRegistrationId() clientRegistrationId}.
4747
* Implementations must return an empty {@code Mono} if authorization is not supported for the specified client,
4848
* e.g. the associated {@link ReactiveOAuth2AuthorizedClientProvider}(s) does not support
4949
* the {@link ClientRegistration#getAuthorizationGrantType() authorization grant} type configured for the client.
5050
*
5151
* <p>
52-
* In the case of re-authorization, implementations must return the provided {@link ServerOAuth2AuthorizeRequest#getAuthorizedClient() authorized client}
52+
* In the case of re-authorization, implementations must return the provided {@link OAuth2AuthorizeRequest#getAuthorizedClient() authorized client}
5353
* if re-authorization is not supported for the client OR is not required,
5454
* e.g. a {@link OAuth2AuthorizedClient#getRefreshToken() refresh token} is not available OR
5555
* the {@link OAuth2AuthorizedClient#getAccessToken() access token} is not expired.
5656
*
5757
* @param authorizeRequest the authorize request
5858
* @return the {@link OAuth2AuthorizedClient} or an empty {@code Mono} if authorization is not supported for the specified client
5959
*/
60-
Mono<OAuth2AuthorizedClient> authorize(ServerOAuth2AuthorizeRequest authorizeRequest);
60+
Mono<OAuth2AuthorizedClient> authorize(OAuth2AuthorizeRequest authorizeRequest);
6161

6262
}

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

+36-4
Original file line numberDiff line numberDiff line change
@@ -18,16 +18,21 @@
1818
import org.springframework.lang.Nullable;
1919
import org.springframework.security.core.Authentication;
2020
import org.springframework.security.oauth2.client.OAuth2AuthorizationContext;
21+
import org.springframework.security.oauth2.client.OAuth2AuthorizeRequest;
2122
import org.springframework.security.oauth2.client.OAuth2AuthorizedClient;
23+
import org.springframework.security.oauth2.client.OAuth2AuthorizedClientManager;
2224
import org.springframework.security.oauth2.client.OAuth2AuthorizedClientProvider;
2325
import org.springframework.security.oauth2.client.registration.ClientRegistration;
2426
import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository;
2527
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
2628
import org.springframework.util.Assert;
2729
import org.springframework.util.StringUtils;
30+
import org.springframework.web.context.request.RequestContextHolder;
31+
import org.springframework.web.context.request.ServletRequestAttributes;
2832

2933
import javax.servlet.http.HttpServletRequest;
3034
import javax.servlet.http.HttpServletResponse;
35+
import java.util.Collections;
3136
import java.util.HashMap;
3237
import java.util.Map;
3338
import java.util.function.Function;
@@ -68,8 +73,11 @@ public OAuth2AuthorizedClient authorize(OAuth2AuthorizeRequest authorizeRequest)
6873
String clientRegistrationId = authorizeRequest.getClientRegistrationId();
6974
OAuth2AuthorizedClient authorizedClient = authorizeRequest.getAuthorizedClient();
7075
Authentication principal = authorizeRequest.getPrincipal();
71-
HttpServletRequest servletRequest = authorizeRequest.getServletRequest();
72-
HttpServletResponse servletResponse = authorizeRequest.getServletResponse();
76+
77+
HttpServletRequest servletRequest = getHttpServletRequestOrDefault(authorizeRequest.getAttributes());
78+
Assert.notNull(servletRequest, "servletRequest cannot be null");
79+
HttpServletResponse servletResponse = getHttpServletResponseOrDefault(authorizeRequest.getAttributes());
80+
Assert.notNull(servletResponse, "servletResponse cannot be null");
7381

7482
OAuth2AuthorizationContext.Builder contextBuilder;
7583
if (authorizedClient != null) {
@@ -104,6 +112,28 @@ public OAuth2AuthorizedClient authorize(OAuth2AuthorizeRequest authorizeRequest)
104112
return authorizedClient;
105113
}
106114

115+
private static HttpServletRequest getHttpServletRequestOrDefault(Map<String, Object> attributes) {
116+
HttpServletRequest servletRequest = (HttpServletRequest) attributes.get(HttpServletRequest.class.getName());
117+
if (servletRequest == null) {
118+
ServletRequestAttributes context = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
119+
if (context != null) {
120+
servletRequest = context.getRequest();
121+
}
122+
}
123+
return servletRequest;
124+
}
125+
126+
private static HttpServletResponse getHttpServletResponseOrDefault(Map<String, Object> attributes) {
127+
HttpServletResponse servletResponse = (HttpServletResponse) attributes.get(HttpServletResponse.class.getName());
128+
if (servletResponse == null) {
129+
ServletRequestAttributes context = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
130+
if (context != null) {
131+
servletResponse = context.getResponse();
132+
}
133+
}
134+
return servletResponse;
135+
}
136+
107137
/**
108138
* Sets the {@link OAuth2AuthorizedClientProvider} used for authorizing (or re-authorizing) an OAuth 2.0 Client.
109139
*
@@ -133,9 +163,11 @@ public static class DefaultContextAttributesMapper implements Function<OAuth2Aut
133163

134164
@Override
135165
public Map<String, Object> apply(OAuth2AuthorizeRequest authorizeRequest) {
136-
Map<String, Object> contextAttributes = new HashMap<>();
137-
String scope = authorizeRequest.getServletRequest().getParameter(OAuth2ParameterNames.SCOPE);
166+
Map<String, Object> contextAttributes = Collections.emptyMap();
167+
HttpServletRequest servletRequest = getHttpServletRequestOrDefault(authorizeRequest.getAttributes());
168+
String scope = servletRequest.getParameter(OAuth2ParameterNames.SCOPE);
138169
if (StringUtils.hasText(scope)) {
170+
contextAttributes = new HashMap<>();
139171
contextAttributes.put(OAuth2AuthorizationContext.REQUEST_SCOPE_ATTRIBUTE_NAME,
140172
StringUtils.delimitedListToStringArray(scope, " "));
141173
}

0 commit comments

Comments
 (0)