1
1
/*
2
- * Copyright 2002-2024 the original author or authors.
2
+ * Copyright 2002-2025 the original author or authors.
3
3
*
4
4
* Licensed under the Apache License, Version 2.0 (the "License");
5
5
* you may not use this file except in compliance with the License.
20
20
import java .util .LinkedHashSet ;
21
21
import java .util .Map ;
22
22
23
+ import org .springframework .context .expression .MapAccessor ;
23
24
import org .springframework .core .ParameterizedTypeReference ;
24
25
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 .StandardEvaluationContext ;
25
29
import org .springframework .http .RequestEntity ;
26
30
import org .springframework .http .ResponseEntity ;
27
31
import org .springframework .security .core .GrantedAuthority ;
57
61
* supported user attribute names.
58
62
*
59
63
* @author Joe Grandja
64
+ * @author Yoobin Yoon
60
65
* @since 5.0
61
66
* @see OAuth2UserService
62
67
* @see OAuth2UserRequest
@@ -71,6 +76,8 @@ public class DefaultOAuth2UserService implements OAuth2UserService<OAuth2UserReq
71
76
72
77
private static final String INVALID_USER_INFO_RESPONSE_ERROR_CODE = "invalid_user_info_response" ;
73
78
79
+ private static final String INVALID_USERNAME_EXPRESSION_ERROR_CODE = "invalid_username_expression" ;
80
+
74
81
private static final ParameterizedTypeReference <Map <String , Object >> PARAMETERIZED_RESPONSE_TYPE = new ParameterizedTypeReference <>() {
75
82
};
76
83
@@ -90,13 +97,69 @@ public DefaultOAuth2UserService() {
90
97
@ Override
91
98
public OAuth2User loadUser (OAuth2UserRequest userRequest ) throws OAuth2AuthenticationException {
92
99
Assert .notNull (userRequest , "userRequest cannot be null" );
93
- String userNameAttributeName = getUserNameAttributeName (userRequest );
100
+ String usernameExpression = getUsernameExpression (userRequest );
94
101
RequestEntity <?> request = this .requestEntityConverter .convert (userRequest );
95
102
ResponseEntity <Map <String , Object >> response = getResponse (userRequest , request );
96
103
OAuth2AccessToken token = userRequest .getAccessToken ();
97
104
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 );
105
+
106
+ String evaluatedUsername = evaluateUsername (attributes , usernameExpression );
107
+ Collection <GrantedAuthority > authorities = getAuthorities (token , attributes , usernameExpression );
108
+
109
+ return DefaultOAuth2User .withUsername (authorities , attributes , evaluatedUsername );
110
+ }
111
+
112
+ private String getUsernameExpression (OAuth2UserRequest userRequest ) {
113
+ if (!StringUtils
114
+ .hasText (userRequest .getClientRegistration ().getProviderDetails ().getUserInfoEndpoint ().getUri ())) {
115
+ OAuth2Error oauth2Error = new OAuth2Error (MISSING_USER_INFO_URI_ERROR_CODE ,
116
+ "Missing required UserInfo Uri in UserInfoEndpoint for Client Registration: "
117
+ + userRequest .getClientRegistration ().getRegistrationId (),
118
+ null );
119
+ throw new OAuth2AuthenticationException (oauth2Error , oauth2Error .toString ());
120
+ }
121
+ String usernameExpression = userRequest .getClientRegistration ()
122
+ .getProviderDetails ()
123
+ .getUserInfoEndpoint ()
124
+ .getUsernameExpression ();
125
+ if (!StringUtils .hasText (usernameExpression )) {
126
+ OAuth2Error oauth2Error = new OAuth2Error (MISSING_USER_NAME_ATTRIBUTE_ERROR_CODE ,
127
+ "Missing required \" user name\" attribute name in UserInfoEndpoint for Client Registration: "
128
+ + userRequest .getClientRegistration ().getRegistrationId (),
129
+ null );
130
+ throw new OAuth2AuthenticationException (oauth2Error , oauth2Error .toString ());
131
+ }
132
+ return usernameExpression ;
133
+ }
134
+
135
+ private String evaluateUsername (Map <String , Object > attributes , String usernameExpression ) {
136
+ Object value = null ;
137
+
138
+ if (attributes .containsKey (usernameExpression )) {
139
+ value = attributes .get (usernameExpression );
140
+ }
141
+ else {
142
+ try {
143
+ ExpressionParser parser = new SpelExpressionParser ();
144
+ StandardEvaluationContext context = new StandardEvaluationContext ();
145
+ context .setRootObject (attributes );
146
+ context .addPropertyAccessor (new MapAccessor ());
147
+ value = parser .parseExpression (usernameExpression ).getValue (context );
148
+ }
149
+ catch (Exception ex ) {
150
+ OAuth2Error oauth2Error = new OAuth2Error (INVALID_USERNAME_EXPRESSION_ERROR_CODE ,
151
+ "Invalid username expression or SPEL expression: " + usernameExpression , null );
152
+ throw new OAuth2AuthenticationException (oauth2Error , oauth2Error .toString (), ex );
153
+ }
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 ();
100
163
}
101
164
102
165
/**
@@ -164,29 +227,6 @@ private ResponseEntity<Map<String, Object>> getResponse(OAuth2UserRequest userRe
164
227
}
165
228
}
166
229
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
-
190
230
private Collection <GrantedAuthority > getAuthorities (OAuth2AccessToken token , Map <String , Object > attributes ,
191
231
String userNameAttributeName ) {
192
232
Collection <GrantedAuthority > authorities = new LinkedHashSet <>();
0 commit comments