Skip to content

Commit f75b51e

Browse files
committed
OAuth2AuthorizeRequest supports attributes
Fixes gh-7341
1 parent 1fc5b27 commit f75b51e

17 files changed

+429
-447
lines changed
Lines changed: 194 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,194 @@
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+
29+
/**
30+
* Represents a request the {@link OAuth2AuthorizedClientManager} uses to
31+
* {@link OAuth2AuthorizedClientManager#authorize(OAuth2AuthorizeRequest) authorize} (or re-authorize)
32+
* the {@link ClientRegistration client} identified by the provided {@link #getClientRegistrationId() clientRegistrationId}.
33+
*
34+
* @author Joe Grandja
35+
* @since 5.2
36+
* @see OAuth2AuthorizedClientManager
37+
*/
38+
public final class OAuth2AuthorizeRequest {
39+
private String clientRegistrationId;
40+
private OAuth2AuthorizedClient authorizedClient;
41+
private Authentication principal;
42+
private Map<String, Object> attributes;
43+
44+
private OAuth2AuthorizeRequest() {
45+
}
46+
47+
/**
48+
* Returns the identifier for the {@link ClientRegistration client registration}.
49+
*
50+
* @return the identifier for the client registration
51+
*/
52+
public String getClientRegistrationId() {
53+
return this.clientRegistrationId;
54+
}
55+
56+
/**
57+
* Returns the {@link OAuth2AuthorizedClient authorized client} or {@code null} if it was not provided.
58+
*
59+
* @return the {@link OAuth2AuthorizedClient} or {@code null} if it was not provided
60+
*/
61+
@Nullable
62+
public OAuth2AuthorizedClient getAuthorizedClient() {
63+
return this.authorizedClient;
64+
}
65+
66+
/**
67+
* Returns the {@code Principal} (to be) associated to the authorized client.
68+
*
69+
* @return the {@code Principal} (to be) associated to the authorized client
70+
*/
71+
public Authentication getPrincipal() {
72+
return this.principal;
73+
}
74+
75+
/**
76+
* Returns the attributes associated to the request.
77+
*
78+
* @return a {@code Map} of the attributes associated to the request
79+
*/
80+
public Map<String, Object> getAttributes() {
81+
return this.attributes;
82+
}
83+
84+
/**
85+
* Returns the value of an attribute associated to the request or {@code null} if not available.
86+
*
87+
* @param name the name of the attribute
88+
* @param <T> the type of the attribute
89+
* @return the value of the attribute associated to the request
90+
*/
91+
@Nullable
92+
@SuppressWarnings("unchecked")
93+
public <T> T getAttribute(String name) {
94+
return (T) this.getAttributes().get(name);
95+
}
96+
97+
/**
98+
* Returns a new {@link Builder} initialized with the identifier for the {@link ClientRegistration client registration}.
99+
*
100+
* @param clientRegistrationId the identifier for the {@link ClientRegistration client registration}
101+
* @return the {@link Builder}
102+
*/
103+
public static Builder withClientRegistrationId(String clientRegistrationId) {
104+
return new Builder(clientRegistrationId);
105+
}
106+
107+
/**
108+
* Returns a new {@link Builder} initialized with the {@link OAuth2AuthorizedClient authorized client}.
109+
*
110+
* @param authorizedClient the {@link OAuth2AuthorizedClient authorized client}
111+
* @return the {@link Builder}
112+
*/
113+
public static Builder withAuthorizedClient(OAuth2AuthorizedClient authorizedClient) {
114+
return new Builder(authorizedClient);
115+
}
116+
117+
/**
118+
* A builder for {@link OAuth2AuthorizeRequest}.
119+
*/
120+
public static class Builder {
121+
private String clientRegistrationId;
122+
private OAuth2AuthorizedClient authorizedClient;
123+
private Authentication principal;
124+
private Map<String, Object> attributes;
125+
126+
private Builder(String clientRegistrationId) {
127+
Assert.hasText(clientRegistrationId, "clientRegistrationId cannot be empty");
128+
this.clientRegistrationId = clientRegistrationId;
129+
}
130+
131+
private Builder(OAuth2AuthorizedClient authorizedClient) {
132+
Assert.notNull(authorizedClient, "authorizedClient cannot be null");
133+
this.authorizedClient = authorizedClient;
134+
}
135+
136+
/**
137+
* Sets the {@code Principal} (to be) associated to the authorized client.
138+
*
139+
* @param principal the {@code Principal} (to be) associated to the authorized client
140+
* @return the {@link Builder}
141+
*/
142+
public Builder principal(Authentication principal) {
143+
this.principal = principal;
144+
return this;
145+
}
146+
147+
/**
148+
* Sets the attributes associated to the request.
149+
*
150+
* @param attributes the attributes associated to the request
151+
* @return the {@link Builder}
152+
*/
153+
public Builder attributes(Map<String, Object> attributes) {
154+
this.attributes = attributes;
155+
return this;
156+
}
157+
158+
/**
159+
* Sets an attribute associated to the request.
160+
*
161+
* @param name the name of the attribute
162+
* @param value the value of the attribute
163+
* @return the {@link Builder}
164+
*/
165+
public Builder attribute(String name, Object value) {
166+
if (this.attributes == null) {
167+
this.attributes = new HashMap<>();
168+
}
169+
this.attributes.put(name, value);
170+
return this;
171+
}
172+
173+
/**
174+
* Builds a new {@link OAuth2AuthorizeRequest}.
175+
*
176+
* @return a {@link OAuth2AuthorizeRequest}
177+
*/
178+
public OAuth2AuthorizeRequest build() {
179+
Assert.notNull(this.principal, "principal cannot be null");
180+
OAuth2AuthorizeRequest authorizeRequest = new OAuth2AuthorizeRequest();
181+
if (this.authorizedClient != null) {
182+
authorizeRequest.clientRegistrationId = this.authorizedClient.getClientRegistration().getRegistrationId();
183+
authorizeRequest.authorizedClient = this.authorizedClient;
184+
} else {
185+
authorizeRequest.clientRegistrationId = this.clientRegistrationId;
186+
}
187+
authorizeRequest.principal = this.principal;
188+
authorizeRequest.attributes = Collections.unmodifiableMap(
189+
CollectionUtils.isEmpty(this.attributes) ?
190+
Collections.emptyMap() : new LinkedHashMap<>(this.attributes));
191+
return authorizeRequest;
192+
}
193+
}
194+
}
Lines changed: 4 additions & 4 deletions
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 {
Lines changed: 5 additions & 6 deletions
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
/**
@@ -43,20 +42,20 @@ public interface ServerOAuth2AuthorizedClientManager {
4342

4443
/**
4544
* Attempt to authorize or re-authorize (if required) the {@link ClientRegistration client}
46-
* identified by the provided {@link ServerOAuth2AuthorizeRequest#getClientRegistrationId() clientRegistrationId}.
45+
* identified by the provided {@link OAuth2AuthorizeRequest#getClientRegistrationId() clientRegistrationId}.
4746
* Implementations must return an empty {@code Mono} if authorization is not supported for the specified client,
4847
* e.g. the associated {@link ReactiveOAuth2AuthorizedClientProvider}(s) does not support
4948
* the {@link ClientRegistration#getAuthorizationGrantType() authorization grant} type configured for the client.
5049
*
5150
* <p>
52-
* In the case of re-authorization, implementations must return the provided {@link ServerOAuth2AuthorizeRequest#getAuthorizedClient() authorized client}
51+
* In the case of re-authorization, implementations must return the provided {@link OAuth2AuthorizeRequest#getAuthorizedClient() authorized client}
5352
* if re-authorization is not supported for the client OR is not required,
5453
* e.g. a {@link OAuth2AuthorizedClient#getRefreshToken() refresh token} is not available OR
5554
* the {@link OAuth2AuthorizedClient#getAccessToken() access token} is not expired.
5655
*
5756
* @param authorizeRequest the authorize request
5857
* @return the {@link OAuth2AuthorizedClient} or an empty {@code Mono} if authorization is not supported for the specified client
5958
*/
60-
Mono<OAuth2AuthorizedClient> authorize(ServerOAuth2AuthorizeRequest authorizeRequest);
59+
Mono<OAuth2AuthorizedClient> authorize(OAuth2AuthorizeRequest authorizeRequest);
6160

6261
}

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

Lines changed: 33 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,17 @@
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;
@@ -69,8 +73,11 @@ public OAuth2AuthorizedClient authorize(OAuth2AuthorizeRequest authorizeRequest)
6973
String clientRegistrationId = authorizeRequest.getClientRegistrationId();
7074
OAuth2AuthorizedClient authorizedClient = authorizeRequest.getAuthorizedClient();
7175
Authentication principal = authorizeRequest.getPrincipal();
72-
HttpServletRequest servletRequest = authorizeRequest.getServletRequest();
73-
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");
7481

7582
OAuth2AuthorizationContext.Builder contextBuilder;
7683
if (authorizedClient != null) {
@@ -105,6 +112,28 @@ public OAuth2AuthorizedClient authorize(OAuth2AuthorizeRequest authorizeRequest)
105112
return authorizedClient;
106113
}
107114

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+
108137
/**
109138
* Sets the {@link OAuth2AuthorizedClientProvider} used for authorizing (or re-authorizing) an OAuth 2.0 Client.
110139
*
@@ -135,7 +164,8 @@ public static class DefaultContextAttributesMapper implements Function<OAuth2Aut
135164
@Override
136165
public Map<String, Object> apply(OAuth2AuthorizeRequest authorizeRequest) {
137166
Map<String, Object> contextAttributes = Collections.emptyMap();
138-
String scope = authorizeRequest.getServletRequest().getParameter(OAuth2ParameterNames.SCOPE);
167+
HttpServletRequest servletRequest = getHttpServletRequestOrDefault(authorizeRequest.getAttributes());
168+
String scope = servletRequest.getParameter(OAuth2ParameterNames.SCOPE);
139169
if (StringUtils.hasText(scope)) {
140170
contextAttributes = new HashMap<>();
141171
contextAttributes.put(OAuth2AuthorizationContext.REQUEST_SCOPE_ATTRIBUTE_NAME,

0 commit comments

Comments
 (0)