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