-
Notifications
You must be signed in to change notification settings - Fork 6.1k
NPE in DefaultOAuth2User.getName function #15338
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Comments
Thanks, @Eccenux, for the report. It sounds like the reason there is an NPE is because the application is placing a If so, I think it's reasonable to check for the if (!attributes.containsKey(nameAttributeKey)) {
throw new IllegalArgumentException("Missing attribute '" + nameAttributeKey + "' in attributes");
} do this: Assert.notNull(attributes.get(nameAttributeKey), "Missing attribute '" + nameAttributeKey + "' in attributes"), Are you able to create a pull request to make this change? |
Would that I mean it's safe to assume this is true: Assert.notEmpty(attributes, "attributes cannot be empty");
Assert.hasText(nameAttributeKey, "nameAttributeKey cannot be empty"); It's something that should be true for valid implementation. On the other hand, what is in |
No, it would not be caught by Spring Security. My suggested change means that passing a map that has a key with a null value is an illegal usage of the class. I wouldn't want business logic to continue in that case. Instead, I'd want the application to address the illegal usage.
/**
* Returns the name of the authenticated <code>Principal</code>. Never
* <code>null</code>.
* @return the name of the authenticated <code>Principal</code>
*/ Because of that, if the value is
Only you know what a key with a null value means in your arrangement, so it would be up to you to check for that condition and then throw a |
So if that happens it's a bug in a different place of Spring Security. My code is not parsing attributes. Spring Security is parsing them. |
For those that stumble upon this I can offer this solution: public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException {
final OAuth2User oauth2user = super.loadUser(userRequest);
final String providerId = userRequest.getClientRegistration().getRegistrationId();
// extra validation of user profile data (as returned by idP)
validateProfile(providerId, oauth2user);
}
/**
* Validate user profile data.
*
* @param providerId code name of the provider (e.g. "google").
* @param oauth2user OAuth profile.
* @throws UsernameNotFoundException invalid profile.
*/
private void validateProfile(final String providerId, final OAuth2User oauth2user) throws UsernameNotFoundException {
String email = oauth2user.getAttribute("Email"); // same as `userNameAttributeName` in the providers config (ClientRegistrationFactoryBean)
if (email == null || email.isEmpty()) {
final String message = "Unexpected profile value (providerId: %s, email: [%s]".formatted(
providerId, email
);
LOG.warn(message);
throw new UsernameNotFoundException(message);
}
} I guess that works too. |
@jzheaux, @Eccenux I'm interested in doing this issue.
I think this is a good way to delegate null check to the user and I agree that the subject of the error is Spring Security, not my code, but I think the user should be able to get a response for giving the wrong value. Because users will not know the need for the I think this is a code that can generate unnecessary resources, and as long as we are aware of this issue, i would like to give users a response to the wrong value through exception. Suggestion:
If the issue is oriented in the direction of delegating null check to the user, I think we can consider modifying it like the format below, but as mentioned in JavaDoc, this is not a good way to do it. public String getName() {
return Objects.isNull(this.getAttribute(this.nameAttributeKey)) ? null : this.getAttribute(this.nameAttributeKey).toString();
}
As @jzheaux said, I think it would be a good way to cause an error to the user by null checking in the I think I'll probably work on it by null checking through the |
Correct me if I'm wrong but if the check is done in a constructor this will actually fail work:
AFAIK that 500 error with NPE skips all your configuration within the application and will display full stack even when you disable presenting it within your application (which can be considered a security problem as it display implementation details). Not sure what happens when the assertion fails. |
For me a good solution would be to throw an error that can be caught. So not a RuntimeException like NPE. I mean NPE can technically be caught, but NPE doesn't have a meaning and is typically not something that is declared in documentation. |
I also thought if the return value for 'getName()' is null, then the constructor of 'DefaultOauth2User' would do null check and throw exception. and then the user handle the result (exception) instead of the null check. If the return value for 'getName()' is null, I thought of doing a null check in the constructor of 'DefaultOuth2User' and throw an exception. Therefore, Our code performs the handle of the results (exceptions) instead of null checks( do this in constructor: Assert.notNull(attributes.get(nameAttributeKey), "Missing attribute '" + nameAttributeKey + "' in attributes"), check this in user code: public class OAuth2UserServiceImpl extends DefaultOAuth2UserService {
private static final Logger LOG = LoggerFactory.getLogger(OAuth2UserServiceImpl.class);
// ...
@Override
public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException {
try {
OAuth2User oauth2user = super.loadUser(userRequest);
final String providerId = userRequest.getClientRegistration().getRegistrationId();
final String username = oauth2user.getName();
…
} catch (IllegalArgumentException e) {
//this part up to user
throw new UsernameNotFoundException(message);
}
}
So, do you mean that instead of NPE or RuntimeException, we should handle null value using more meaningful exceptions such as 'UserNotFoundException'? |
Yes, any meaningful Exception extending an |
- fix name attribute value check method - add test case when nameAttributeValue is null
@jzheaux I have made a PR, can you check and merge please? I wanted to treat it as a more meaningful exception, but I thought the preferred method in Spring Security was a check using If you think it is necessary to deal with a more meaningful exception, i will supplement the work later. |
I hope you know that using I think it's not well documented that one should do: try {
OAuth2User oauth2user = super.loadUser(userRequest);
} catch (IllegalArgumentException e) {
...
}
|
public DefaultOAuth2User(..) {
Assert.notEmpty(attributes, "attributes cannot be empty");
Assert.hasText(nameAttributeKey, "nameAttributeKey cannot be empty");
if (!attributes.containsKey(nameAttributeKey)) {
throw new IllegalArgumentException("Missing attribute '" + nameAttributeKey + "' in attributes");
} On
I understand your point but not sure we need to add Let's wait maintainer's opinion too~! |
Thanks, no, I think
These would be less meaningful because they would intimate that there is some problem with authentication, which there isn't, at least not in the constructor. The problem is that an application is handing an invalid value to a component. The additional meaning that you are deriving here is usage-specific. For example, consider that
This is a result of how you are using the class. It should throw a 500 because that indicates that it is the server's responsibility to address the issue. It ensures that you address the issue quickly by making sure that your application does not hand null values to The way I would recommend an application address the fact that the IdP gave a null value for the user name would be handle things before they reach DefaultOAuth2UserService service = new DefaultOAuth2UserService();
RestTemplate rest = new RestTemplate();
rest.setErrorHandler(new OAuth2ErrorResponseErrorHandler());
rest.setMessageConverters(List.of(new MyMissingUsernameAdaptingHttpMessageConverter()));
service.setRestOperations(rest); |
Uh oh!
There was an error while loading. Please reload this page.
Describe the bug
Problem is a possible
NullPointerException
(NPE) inDefaultOAuth2User.getName
.To Reproduce
Get null value on the name attribute during after OAuth authentaction.
Or simply create attributes for
DefaultOAuth2User
as show in the Sample.Expected behavior
NPE shouldn't be going around Spring Security. It should maybe use
throw new UsernameNotFoundException(.)
, not sure.Sample
This is a minimal example to reproduce (without setting up an oauth2 server):
This code is a problem:
Real code
In a real use case this was the attributes of the DefaultOAuth2User:
The name field was "Email", but I guess it could be anything that just so happens to be null. Note that attributes are from an external service. The service is defined by
userInfoUri
fromFactoryBean<T>
. So the attributes are essentially attributes of a user profile as returned by the identity provider (idP).This is how getName was originally called:
The text was updated successfully, but these errors were encountered: