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 .SimpleEvaluationContext ;
25
29
import org .springframework .http .RequestEntity ;
26
30
import org .springframework .http .ResponseEntity ;
27
31
import org .springframework .security .core .GrantedAuthority ;
47
51
* An implementation of an {@link OAuth2UserService} that supports standard OAuth 2.0
48
52
* Provider's.
49
53
* <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 ()}.
54
58
* <p>
55
59
* <b>NOTE:</b> Attribute names are <b>not</b> standardized between providers and
56
60
* therefore will vary. Please consult the provider's API documentation for the set of
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,10 @@ 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
+
81
+ private static final ExpressionParser expressionParser = new SpelExpressionParser ();
82
+
74
83
private static final ParameterizedTypeReference <Map <String , Object >> PARAMETERIZED_RESPONSE_TYPE = new ParameterizedTypeReference <>() {
75
84
};
76
85
@@ -90,13 +99,67 @@ public DefaultOAuth2UserService() {
90
99
@ Override
91
100
public OAuth2User loadUser (OAuth2UserRequest userRequest ) throws OAuth2AuthenticationException {
92
101
Assert .notNull (userRequest , "userRequest cannot be null" );
93
- String userNameAttributeName = getUserNameAttributeName (userRequest );
102
+ String usernameExpression = getUsernameExpression (userRequest );
94
103
RequestEntity <?> request = this .requestEntityConverter .convert (userRequest );
95
104
ResponseEntity <Map <String , Object >> response = getResponse (userRequest , request );
96
105
OAuth2AccessToken token = userRequest .getAccessToken ();
97
106
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 ();
100
163
}
101
164
102
165
/**
@@ -164,33 +227,11 @@ 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
- String userNameAttributeName ) {
231
+ String username ) {
192
232
Collection <GrantedAuthority > authorities = new LinkedHashSet <>();
193
- authorities .add (new OAuth2UserAuthority (attributes , userNameAttributeName ));
233
+ authorities .add (OAuth2UserAuthority .withUsername (username ).attributes (attributes ).build ());
234
+
194
235
for (String authority : token .getScopes ()) {
195
236
authorities .add (new SimpleGrantedAuthority ("SCOPE_" + authority ));
196
237
}
0 commit comments