Skip to content

Commit e576f13

Browse files
committed
Remove deprecated constructor/method usage in OAuth2 user services
Replace deprecated constructor and method calls with new builder pattern and updated APIs to eliminate deprecated code usage - Update OidcUserRequestUtils to use non-deprecated APIs - Extract OAuth2UsernameExpressionUtils for SpEL evaluation logic - minor fixes for clarity closes gh-16390
1 parent 9c60779 commit e576f13

File tree

10 files changed

+175
-125
lines changed

10 files changed

+175
-125
lines changed

oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/oidc/userinfo/OidcReactiveOAuth2UserService.java

Lines changed: 13 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2024 the original author or authors.
2+
* Copyright 2002-2025 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.
@@ -198,23 +198,22 @@ public final void setRetrieveUserInfo(Predicate<OidcUserRequest> retrieveUserInf
198198
* var accessToken = userRequest.getAccessToken();
199199
* var grantedAuthorities = new HashSet&lt;GrantedAuthority&gt;();
200200
* // TODO: Map authorities from the access token
201-
* var userNameAttributeName = "preferred_username";
202-
* return Mono.just(new DefaultOidcUser(
203-
* grantedAuthorities,
204-
* userRequest.getIdToken(),
205-
* userInfo,
206-
* userNameAttributeName
207-
* ));
201+
* var username = "preferred_username";
202+
* return Mono.just(DefaultOidcUser.withUsername(username)
203+
* .authorities(grantedAuthorities)
204+
* .idToken(userRequest.getIdToken())
205+
* .userInfo(userInfo)
206+
* .build());
208207
* };
209208
* }
210209
* </pre>
211210
* <p>
212-
* Note that you can access the {@code userNameAttributeName} via the
213-
* {@link ClientRegistration} as follows: <pre>
214-
* var userNameAttributeName = userRequest.getClientRegistration()
215-
* .getProviderDetails()
216-
* .getUserInfoEndpoint()
217-
* .getUserNameAttributeName();
211+
* Note that you can access the username expression via the {@link ClientRegistration}
212+
* as follows: <pre>
213+
* var usernameExpression = userRequest.getClientRegistration()
214+
* .getProviderDetails()
215+
* .getUserInfoEndpoint()
216+
* .getUsernameExpression();
218217
* </pre>
219218
* <p>
220219
* By default, a {@link DefaultOidcUser} is created with authorities mapped as

oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/oidc/userinfo/OidcUserRequestUtils.java

Lines changed: 33 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2024 the original author or authors.
2+
* Copyright 2002-2025 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.
@@ -16,25 +16,31 @@
1616

1717
package org.springframework.security.oauth2.client.oidc.userinfo;
1818

19+
import java.util.HashMap;
1920
import java.util.LinkedHashSet;
21+
import java.util.Map;
2022
import java.util.Set;
2123

2224
import org.springframework.security.core.GrantedAuthority;
2325
import org.springframework.security.core.authority.SimpleGrantedAuthority;
2426
import org.springframework.security.oauth2.client.registration.ClientRegistration;
27+
import org.springframework.security.oauth2.client.userinfo.OAuth2UsernameExpressionUtils;
2528
import org.springframework.security.oauth2.core.AuthorizationGrantType;
2629
import org.springframework.security.oauth2.core.OAuth2AccessToken;
30+
import org.springframework.security.oauth2.core.oidc.OidcIdToken;
2731
import org.springframework.security.oauth2.core.oidc.OidcUserInfo;
2832
import org.springframework.security.oauth2.core.oidc.user.DefaultOidcUser;
2933
import org.springframework.security.oauth2.core.oidc.user.OidcUser;
3034
import org.springframework.security.oauth2.core.oidc.user.OidcUserAuthority;
35+
import org.springframework.util.Assert;
3136
import org.springframework.util.CollectionUtils;
3237
import org.springframework.util.StringUtils;
3338

3439
/**
3540
* Utilities for working with the {@link OidcUserRequest}
3641
*
3742
* @author Rob Winch
43+
* @author Yoobin Yoon
3844
* @since 5.1
3945
*/
4046
final class OidcUserRequestUtils {
@@ -79,21 +85,40 @@ static boolean shouldRetrieveUserInfo(OidcUserRequest userRequest) {
7985
static OidcUser getUser(OidcUserRequest userRequest, OidcUserInfo userInfo) {
8086
Set<GrantedAuthority> authorities = new LinkedHashSet<>();
8187
ClientRegistration.ProviderDetails providerDetails = userRequest.getClientRegistration().getProviderDetails();
82-
String userNameAttributeName = providerDetails.getUserInfoEndpoint().getUserNameAttributeName();
83-
if (StringUtils.hasText(userNameAttributeName)) {
84-
authorities.add(new OidcUserAuthority(userRequest.getIdToken(), userInfo, userNameAttributeName));
88+
String usernameExpression = providerDetails.getUserInfoEndpoint().getUsernameExpression();
89+
90+
String username;
91+
if (StringUtils.hasText(usernameExpression)) {
92+
Map<String, Object> claims = collectClaims(userRequest.getIdToken(), userInfo);
93+
username = OAuth2UsernameExpressionUtils.evaluateUsername(claims, usernameExpression);
8594
}
8695
else {
87-
authorities.add(new OidcUserAuthority(userRequest.getIdToken(), userInfo));
96+
username = userRequest.getIdToken().getSubject();
8897
}
98+
99+
authorities
100+
.add(OidcUserAuthority.withUsername(username).idToken(userRequest.getIdToken()).userInfo(userInfo).build());
101+
89102
OAuth2AccessToken token = userRequest.getAccessToken();
90103
for (String scope : token.getScopes()) {
91104
authorities.add(new SimpleGrantedAuthority("SCOPE_" + scope));
92105
}
93-
if (StringUtils.hasText(userNameAttributeName)) {
94-
return new DefaultOidcUser(authorities, userRequest.getIdToken(), userInfo, userNameAttributeName);
106+
107+
return DefaultOidcUser.withUsername(username)
108+
.authorities(authorities)
109+
.idToken(userRequest.getIdToken())
110+
.userInfo(userInfo)
111+
.build();
112+
}
113+
114+
private static Map<String, Object> collectClaims(OidcIdToken idToken, OidcUserInfo userInfo) {
115+
Assert.notNull(idToken, "idToken cannot be null");
116+
Map<String, Object> claims = new HashMap<>();
117+
if (userInfo != null) {
118+
claims.putAll(userInfo.getClaims());
95119
}
96-
return new DefaultOidcUser(authorities, userRequest.getIdToken(), userInfo);
120+
claims.putAll(idToken.getClaims());
121+
return claims;
97122
}
98123

99124
private OidcUserRequestUtils() {

oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/oidc/userinfo/OidcUserService.java

Lines changed: 13 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2024 the original author or authors.
2+
* Copyright 2002-2025 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.
@@ -262,23 +262,22 @@ public final void setRetrieveUserInfo(Predicate<OidcUserRequest> retrieveUserInf
262262
* var accessToken = userRequest.getAccessToken();
263263
* var grantedAuthorities = new HashSet&lt;GrantedAuthority&gt;();
264264
* // TODO: Map authorities from the access token
265-
* var userNameAttributeName = "preferred_username";
266-
* return new DefaultOidcUser(
267-
* grantedAuthorities,
268-
* userRequest.getIdToken(),
269-
* userInfo,
270-
* userNameAttributeName
271-
* );
265+
* var username = "preferred_username";
266+
* return DefaultOidcUser.withUsername(username)
267+
* .authorities(grantedAuthorities)
268+
* .idToken(userRequest.getIdToken())
269+
* .userInfo(userInfo)
270+
* .build();
272271
* };
273272
* }
274273
* </pre>
275274
* <p>
276-
* Note that you can access the {@code userNameAttributeName} via the
277-
* {@link ClientRegistration} as follows: <pre>
278-
* var userNameAttributeName = userRequest.getClientRegistration()
279-
* .getProviderDetails()
280-
* .getUserInfoEndpoint()
281-
* .getUserNameAttributeName();
275+
* Note that you can access the username expression via the {@link ClientRegistration}
276+
* as follows: <pre>
277+
* var usernameExpression = userRequest.getClientRegistration()
278+
* .getProviderDetails()
279+
* .getUserInfoEndpoint()
280+
* .getUsernameExpression();
282281
* </pre>
283282
* <p>
284283
* By default, a {@link DefaultOidcUser} is created with authorities mapped as

oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/userinfo/DefaultOAuth2UserService.java

Lines changed: 3 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -20,12 +20,8 @@
2020
import java.util.LinkedHashSet;
2121
import java.util.Map;
2222

23-
import org.springframework.context.expression.MapAccessor;
2423
import org.springframework.core.ParameterizedTypeReference;
2524
import org.springframework.core.convert.converter.Converter;
26-
import org.springframework.expression.ExpressionParser;
27-
import org.springframework.expression.spel.standard.SpelExpressionParser;
28-
import org.springframework.expression.spel.support.SimpleEvaluationContext;
2925
import org.springframework.http.RequestEntity;
3026
import org.springframework.http.ResponseEntity;
3127
import org.springframework.security.core.GrantedAuthority;
@@ -76,10 +72,6 @@ public class DefaultOAuth2UserService implements OAuth2UserService<OAuth2UserReq
7672

7773
private static final String INVALID_USER_INFO_RESPONSE_ERROR_CODE = "invalid_user_info_response";
7874

79-
private static final String INVALID_USERNAME_EXPRESSION_ERROR_CODE = "invalid_username_expression";
80-
81-
private static final ExpressionParser expressionParser = new SpelExpressionParser();
82-
8375
private static final ParameterizedTypeReference<Map<String, Object>> PARAMETERIZED_RESPONSE_TYPE = new ParameterizedTypeReference<>() {
8476
};
8577

@@ -104,15 +96,9 @@ public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2Authentic
10496
ResponseEntity<Map<String, Object>> response = getResponse(userRequest, request);
10597
OAuth2AccessToken token = userRequest.getAccessToken();
10698
Map<String, Object> attributes = this.attributesConverter.convert(userRequest).convert(response.getBody());
107-
108-
String evaluatedUsername = evaluateUsername(attributes, usernameExpression);
109-
110-
Collection<GrantedAuthority> authorities = getAuthorities(token, attributes, evaluatedUsername);
111-
112-
return DefaultOAuth2User.withUsername(evaluatedUsername)
113-
.authorities(authorities)
114-
.attributes(attributes)
115-
.build();
99+
String username = OAuth2UsernameExpressionUtils.evaluateUsername(attributes, usernameExpression);
100+
Collection<GrantedAuthority> authorities = getAuthorities(token, attributes, username);
101+
return DefaultOAuth2User.withUsername(username).authorities(authorities).attributes(attributes).build();
116102
}
117103

118104
private String getUsernameExpression(OAuth2UserRequest userRequest) {
@@ -138,30 +124,6 @@ private String getUsernameExpression(OAuth2UserRequest userRequest) {
138124
return usernameExpression;
139125
}
140126

141-
private String evaluateUsername(Map<String, Object> attributes, String usernameExpression) {
142-
Object value = null;
143-
144-
try {
145-
SimpleEvaluationContext context = SimpleEvaluationContext.forPropertyAccessors(new MapAccessor())
146-
.withRootObject(attributes)
147-
.build();
148-
value = expressionParser.parseExpression(usernameExpression).getValue(context);
149-
}
150-
catch (Exception ex) {
151-
OAuth2Error oauth2Error = new OAuth2Error(INVALID_USERNAME_EXPRESSION_ERROR_CODE,
152-
"Invalid username expression or SPEL expression: " + usernameExpression, null);
153-
throw new OAuth2AuthenticationException(oauth2Error, oauth2Error.toString(), ex);
154-
}
155-
156-
if (value == null) {
157-
OAuth2Error oauth2Error = new OAuth2Error(INVALID_USER_INFO_RESPONSE_ERROR_CODE,
158-
"An error occurred while attempting to retrieve the UserInfo Resource: username cannot be null",
159-
null);
160-
throw new OAuth2AuthenticationException(oauth2Error, oauth2Error.toString());
161-
}
162-
return value.toString();
163-
}
164-
165127
/**
166128
* Use this strategy to adapt user attributes into a format understood by Spring
167129
* Security; by default, the original attributes are preserved.

oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/userinfo/DefaultReactiveOAuth2UserService.java

Lines changed: 1 addition & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -25,12 +25,8 @@
2525
import net.minidev.json.JSONObject;
2626
import reactor.core.publisher.Mono;
2727

28-
import org.springframework.context.expression.MapAccessor;
2928
import org.springframework.core.ParameterizedTypeReference;
3029
import org.springframework.core.convert.converter.Converter;
31-
import org.springframework.expression.ExpressionParser;
32-
import org.springframework.expression.spel.standard.SpelExpressionParser;
33-
import org.springframework.expression.spel.support.SimpleEvaluationContext;
3430
import org.springframework.http.HttpHeaders;
3531
import org.springframework.http.HttpStatusCode;
3632
import org.springframework.http.MediaType;
@@ -78,10 +74,6 @@ public class DefaultReactiveOAuth2UserService implements ReactiveOAuth2UserServi
7874

7975
private static final String MISSING_USER_NAME_ATTRIBUTE_ERROR_CODE = "missing_user_name_attribute";
8076

81-
private static final String INVALID_USERNAME_EXPRESSION_ERROR_CODE = "invalid_username_expression";
82-
83-
private static final ExpressionParser expressionParser = new SpelExpressionParser();
84-
8577
private static final ParameterizedTypeReference<Map<String, Object>> STRING_OBJECT_MAP = new ParameterizedTypeReference<>() {
8678
};
8779

@@ -129,7 +121,7 @@ public Mono<OAuth2User> loadUser(OAuth2UserRequest userRequest) throws OAuth2Aut
129121
.bodyToMono(DefaultReactiveOAuth2UserService.STRING_OBJECT_MAP)
130122
.mapNotNull((attributes) -> this.attributesConverter.convert(userRequest).convert(attributes));
131123
return userAttributes.map((attrs) -> {
132-
String username = evaluateUsername(attrs, usernameExpression);
124+
String username = OAuth2UsernameExpressionUtils.evaluateUsername(attrs, usernameExpression);
133125
Set<GrantedAuthority> authorities = new HashSet<>();
134126
authorities.add(OAuth2UserAuthority.withUsername(username)
135127
.attributes(attrs)
@@ -187,32 +179,6 @@ private String getUsernameExpression(OAuth2UserRequest userRequest) {
187179
return usernameExpression;
188180
}
189181

190-
private String evaluateUsername(Map<String, Object> attributes, String usernameExpression) {
191-
Object value = null;
192-
193-
try {
194-
SimpleEvaluationContext context = SimpleEvaluationContext.forPropertyAccessors(new MapAccessor())
195-
.withRootObject(attributes)
196-
.build();
197-
value = expressionParser.parseExpression(usernameExpression).getValue(context);
198-
}
199-
catch (Exception ex) {
200-
OAuth2Error oauth2Error = new OAuth2Error(INVALID_USERNAME_EXPRESSION_ERROR_CODE,
201-
"Invalid username expression or SPEL expression: " + usernameExpression, null);
202-
throw new OAuth2AuthenticationException(oauth2Error, oauth2Error.toString(), ex);
203-
204-
}
205-
206-
if (value == null) {
207-
OAuth2Error oauth2Error = new OAuth2Error(INVALID_USER_INFO_RESPONSE_ERROR_CODE,
208-
"An error occurred while attempting to retrieve the UserInfo Resource: username cannot be null",
209-
null);
210-
throw new OAuth2AuthenticationException(oauth2Error, oauth2Error.toString());
211-
}
212-
213-
return value.toString();
214-
}
215-
216182
private WebClient.RequestHeadersSpec<?> getRequestHeaderSpec(OAuth2UserRequest userRequest, String userInfoUri,
217183
AuthenticationMethod authenticationMethod) {
218184
if (AuthenticationMethod.FORM.equals(authenticationMethod)) {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
/*
2+
* Copyright 2002-2025 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+
17+
package org.springframework.security.oauth2.client.userinfo;
18+
19+
import java.util.Map;
20+
21+
import org.springframework.context.expression.MapAccessor;
22+
import org.springframework.expression.ExpressionParser;
23+
import org.springframework.expression.spel.standard.SpelExpressionParser;
24+
import org.springframework.expression.spel.support.SimpleEvaluationContext;
25+
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
26+
import org.springframework.security.oauth2.core.OAuth2Error;
27+
28+
/**
29+
* Utility class for evaluating username expressions in OAuth2 user information.
30+
*
31+
* @author Yoobin Yoon
32+
* @since 7.0
33+
*/
34+
public final class OAuth2UsernameExpressionUtils {
35+
36+
private static final String INVALID_USERNAME_EXPRESSION_ERROR_CODE = "invalid_username_expression";
37+
38+
private static final String INVALID_USER_INFO_RESPONSE_ERROR_CODE = "invalid_user_info_response";
39+
40+
private static final ExpressionParser expressionParser = new SpelExpressionParser();
41+
42+
/**
43+
* Evaluates a SpEL expression to extract the username from user attributes.
44+
*
45+
* <p>
46+
* Examples:
47+
* <ul>
48+
* <li>Simple attribute: {@code "username"} or {@code "['username']"}</li>
49+
* <li>Nested attribute: {@code "data.username"}</li>
50+
* <li>Complex expression: {@code "user_info?.name ?: 'anonymous'"}</li>
51+
* </ul>
52+
* @param attributes the user attributes (used as SpEL root object)
53+
* @param usernameExpression the SpEL expression to evaluate
54+
* @return the evaluated username (never null)
55+
* @throws OAuth2AuthenticationException if expression is invalid or evaluates to null
56+
*/
57+
public static String evaluateUsername(Map<String, Object> attributes, String usernameExpression) {
58+
Object value = null;
59+
60+
try {
61+
SimpleEvaluationContext context = SimpleEvaluationContext.forPropertyAccessors(new MapAccessor())
62+
.withRootObject(attributes)
63+
.build();
64+
value = expressionParser.parseExpression(usernameExpression).getValue(context);
65+
}
66+
catch (Exception ex) {
67+
OAuth2Error oauth2Error = new OAuth2Error(INVALID_USERNAME_EXPRESSION_ERROR_CODE,
68+
"Invalid username expression or SPEL expression: " + usernameExpression, null);
69+
throw new OAuth2AuthenticationException(oauth2Error, oauth2Error.toString(), ex);
70+
}
71+
72+
if (value == null) {
73+
OAuth2Error oauth2Error = new OAuth2Error(INVALID_USER_INFO_RESPONSE_ERROR_CODE,
74+
"An error occurred while attempting to retrieve the UserInfo Resource: username cannot be null",
75+
null);
76+
throw new OAuth2AuthenticationException(oauth2Error, oauth2Error.toString());
77+
}
78+
return value.toString();
79+
}
80+
81+
private OAuth2UsernameExpressionUtils() {
82+
}
83+
84+
}

0 commit comments

Comments
 (0)