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.
2020import java .util .LinkedHashSet ;
2121import java .util .Map ;
2222
23+ import org .springframework .context .expression .MapAccessor ;
2324import org .springframework .core .ParameterizedTypeReference ;
2425import 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 ;
2529import org .springframework .http .RequestEntity ;
2630import org .springframework .http .ResponseEntity ;
2731import org .springframework .security .core .GrantedAuthority ;
4751 * An implementation of an {@link OAuth2UserService} that supports standard OAuth 2.0
4852 * Provider's.
4953 * <p>
50- * For standard OAuth 2.0 Provider's, the attribute name used to access the user's name
51- * from the UserInfo response is required and therefore must be available via
52- * {@link ClientRegistration.ProviderDetails.UserInfoEndpoint#getUserNameAttributeName ()
53- * UserInfoEndpoint.getUserNameAttributeName ()}.
54+ * For standard OAuth 2.0 Provider's, the username expression used to extract the user's
55+ * name from the UserInfo response is required and therefore must be available via
56+ * {@link ClientRegistration.ProviderDetails.UserInfoEndpoint#getUsernameExpression ()
57+ * UserInfoEndpoint.getUsernameExpression ()}.
5458 * <p>
5559 * <b>NOTE:</b> Attribute names are <b>not</b> standardized between providers and
5660 * therefore will vary. Please consult the provider's API documentation for the set of
5761 * supported user attribute names.
5862 *
5963 * @author Joe Grandja
64+ * @author Yoobin Yoon
6065 * @since 5.0
6166 * @see OAuth2UserService
6267 * @see OAuth2UserRequest
@@ -71,6 +76,10 @@ public class DefaultOAuth2UserService implements OAuth2UserService<OAuth2UserReq
7176
7277 private static final String INVALID_USER_INFO_RESPONSE_ERROR_CODE = "invalid_user_info_response" ;
7378
79+ private static final String INVALID_USERNAME_EXPRESSION_ERROR_CODE = "invalid_username_expression" ;
80+
81+ private static final ExpressionParser expressionParser = new SpelExpressionParser ();
82+
7483 private static final ParameterizedTypeReference <Map <String , Object >> PARAMETERIZED_RESPONSE_TYPE = new ParameterizedTypeReference <>() {
7584 };
7685
@@ -90,13 +99,67 @@ public DefaultOAuth2UserService() {
9099 @ Override
91100 public OAuth2User loadUser (OAuth2UserRequest userRequest ) throws OAuth2AuthenticationException {
92101 Assert .notNull (userRequest , "userRequest cannot be null" );
93- String userNameAttributeName = getUserNameAttributeName (userRequest );
102+ String usernameExpression = getUsernameExpression (userRequest );
94103 RequestEntity <?> request = this .requestEntityConverter .convert (userRequest );
95104 ResponseEntity <Map <String , Object >> response = getResponse (userRequest , request );
96105 OAuth2AccessToken token = userRequest .getAccessToken ();
97106 Map <String , Object > attributes = this .attributesConverter .convert (userRequest ).convert (response .getBody ());
98- Collection <GrantedAuthority > authorities = getAuthorities (token , attributes , userNameAttributeName );
99- return new DefaultOAuth2User (authorities , attributes , userNameAttributeName );
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 ();
116+ }
117+
118+ private String getUsernameExpression (OAuth2UserRequest userRequest ) {
119+ if (!StringUtils
120+ .hasText (userRequest .getClientRegistration ().getProviderDetails ().getUserInfoEndpoint ().getUri ())) {
121+ OAuth2Error oauth2Error = new OAuth2Error (MISSING_USER_INFO_URI_ERROR_CODE ,
122+ "Missing required UserInfo Uri in UserInfoEndpoint for Client Registration: "
123+ + userRequest .getClientRegistration ().getRegistrationId (),
124+ null );
125+ throw new OAuth2AuthenticationException (oauth2Error , oauth2Error .toString ());
126+ }
127+ String usernameExpression = userRequest .getClientRegistration ()
128+ .getProviderDetails ()
129+ .getUserInfoEndpoint ()
130+ .getUsernameExpression ();
131+ if (!StringUtils .hasText (usernameExpression )) {
132+ OAuth2Error oauth2Error = new OAuth2Error (MISSING_USER_NAME_ATTRIBUTE_ERROR_CODE ,
133+ "Missing required \" user name\" attribute name in UserInfoEndpoint for Client Registration: "
134+ + userRequest .getClientRegistration ().getRegistrationId (),
135+ null );
136+ throw new OAuth2AuthenticationException (oauth2Error , oauth2Error .toString ());
137+ }
138+ return usernameExpression ;
139+ }
140+
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 ();
100163 }
101164
102165 /**
@@ -164,33 +227,11 @@ private ResponseEntity<Map<String, Object>> getResponse(OAuth2UserRequest userRe
164227 }
165228 }
166229
167- private String getUserNameAttributeName (OAuth2UserRequest userRequest ) {
168- if (!StringUtils
169- .hasText (userRequest .getClientRegistration ().getProviderDetails ().getUserInfoEndpoint ().getUri ())) {
170- OAuth2Error oauth2Error = new OAuth2Error (MISSING_USER_INFO_URI_ERROR_CODE ,
171- "Missing required UserInfo Uri in UserInfoEndpoint for Client Registration: "
172- + userRequest .getClientRegistration ().getRegistrationId (),
173- null );
174- throw new OAuth2AuthenticationException (oauth2Error , oauth2Error .toString ());
175- }
176- String userNameAttributeName = userRequest .getClientRegistration ()
177- .getProviderDetails ()
178- .getUserInfoEndpoint ()
179- .getUserNameAttributeName ();
180- if (!StringUtils .hasText (userNameAttributeName )) {
181- OAuth2Error oauth2Error = new OAuth2Error (MISSING_USER_NAME_ATTRIBUTE_ERROR_CODE ,
182- "Missing required \" user name\" attribute name in UserInfoEndpoint for Client Registration: "
183- + userRequest .getClientRegistration ().getRegistrationId (),
184- null );
185- throw new OAuth2AuthenticationException (oauth2Error , oauth2Error .toString ());
186- }
187- return userNameAttributeName ;
188- }
189-
190230 private Collection <GrantedAuthority > getAuthorities (OAuth2AccessToken token , Map <String , Object > attributes ,
191- String userNameAttributeName ) {
231+ String username ) {
192232 Collection <GrantedAuthority > authorities = new LinkedHashSet <>();
193- authorities .add (new OAuth2UserAuthority (attributes , userNameAttributeName ));
233+ authorities .add (OAuth2UserAuthority .withUsername (username ).attributes (attributes ).build ());
234+
194235 for (String authority : token .getScopes ()) {
195236 authorities .add (new SimpleGrantedAuthority ("SCOPE_" + authority ));
196237 }
0 commit comments