Description
Issue Description:
Currently, the DefaultOAuth2UserService
seems to expect the userNameAttributeName
configured in the ClientRegistration
to be a top-level attribute in the JSON response from the UserInfo endpoint. However, some OAuth2 providers return user information nested within a structured object (e.g., under a data
key).
Example UserInfo Response:
Some providers, like Feishu/Lark, return a UserInfo response structured like this (https://open.feishu.cn/document/server-docs/authentication-management/login-state-management/get):
{
"code": 0,
"msg": "success",
"data": {
"name": "zhangsan",
"en_name": "zhangsan",
"avatar_url": "www.feishu.cn/avatar/icon",
"avatar_thumb": "www.feishu.cn/avatar/icon_thumb",
"avatar_middle": "www.feishu.cn/avatar/icon_middle",
"avatar_big": "www.feishu.cn/avatar/icon_big",
"open_id": "ou-caecc734c2e3328a62489fe0648c4b98779515d3",
"union_id": "on-d89jhsdhjsajkda7828enjdj328ydhhw3u43yjhdj",
"email": "[email protected]",
"enterprise_email": "[email protected]",
"user_id": "5d9bdxxx",
"mobile": "+86130002883xx",
"tenant_key": "736588c92lxf175d",
"employee_no": "111222333"
}
}
In this case, the desired unique identifier for the user might be open_id
, which is located at data.open_id
.
Expected Behavior
It should be possible to configure the userNameAttributeName
to specify a path to a nested attribute. For instance, configuring userNameAttributeName
as data.open_id
(using dot notation or a similar standard like JSON Pointer /data/open_id
) should instruct the OAuth2UserService
to extract the value "ou-caecc734c2e3328a62489fe0648c4b98779515d3"
from the example JSON and use it as the principal name (i.e., the value returned by OAuth2User.getName()
).
Current Behavior
Based on the code in DefaultOAuth2UserService
(and how DefaultOAuth2User
uses the userNameAttributeName
), it appears the framework expects userNameAttributeName
to be a direct key in the top-level userAttributes
map.
// Relevant snippet from DefaultOAuth2UserService
// ...
String userNameAttributeName = userRequest.getClientRegistration()
.getProviderDetails()
.getUserInfoEndpoint()
.getUserNameAttributeName();
// ...
Map<String, Object> userAttributes = response.getBody();
// ...
return new DefaultOAuth2User(authorities, userAttributes, userNameAttributeName);
// DefaultOAuth2User likely uses userAttributes.get(userNameAttributeName) internally
Trying to set userNameAttributeName
to data.open_id
likely results in the principal name being null
or an error, because there is no top-level key named "data.open_id"
in the userAttributes
map. The framework does not seem to parse nested paths for this attribute.
Context
- How has this issue affected you? This limitation prevents straightforward integration with OAuth2 providers whose UserInfo endpoints return essential user identifiers within nested JSON objects. We are trying to integrate with a provider (like Feishu/Lark) that uses this nested structure.
- What are you trying to accomplish? We need to reliably extract the
open_id
from the nesteddata
object in the UserInfo response and use it as the primary user identifier within our Spring Security context. - What other alternatives have you considered?
- Implementing a custom
OAuth2UserService
to manually parse the response and extract the nested attribute. This works but adds boilerplate code for a relatively common scenario.
- Implementing a custom
- Are you aware of any workarounds? The primary workaround is creating a custom
OAuth2UserService
, as mentioned above. However, native support in the framework would be cleaner and more convenient.
Suggestion:
Consider enhancing the DefaultOAuth2UserService
(or related components) to support a path expression (e.g., dot notation data.open_id
or JSON Pointer /data/open_id
) for the userNameAttributeName
configuration, allowing developers to easily specify nested attributes as the user identifier.