Validate sub claim in UserInfo Response

Fixes gh-5447
This commit is contained in:
Joe Grandja 2018-06-25 16:44:04 -04:00
parent ec970c9b8e
commit d32aa3c6d6
2 changed files with 43 additions and 5 deletions

View File

@ -15,10 +15,6 @@
*/
package org.springframework.security.oauth2.client.oidc.userinfo;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.oauth2.client.userinfo.DefaultOAuth2UserService;
import org.springframework.security.oauth2.client.userinfo.OAuth2UserRequest;
@ -36,6 +32,10 @@ import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
/**
* An implementation of an {@link OAuth2UserService} that supports OpenID Connect 1.0 Provider's.
*
@ -62,7 +62,14 @@ public class OidcUserService implements OAuth2UserService<OidcUserRequest, OidcU
userInfo = new OidcUserInfo(oauth2User.getAttributes());
// http://openid.net/specs/openid-connect-core-1_0.html#UserInfoResponse
// Due to the possibility of token substitution attacks (see Section 16.11),
// 1) The sub (subject) Claim MUST always be returned in the UserInfo Response
if (userInfo.getSubject() == null) {
OAuth2Error oauth2Error = new OAuth2Error(INVALID_USER_INFO_RESPONSE_ERROR_CODE);
throw new OAuth2AuthenticationException(oauth2Error, oauth2Error.toString());
}
// 2) Due to the possibility of token substitution attacks (see Section 16.11),
// the UserInfo Response is not guaranteed to be about the End-User
// identified by the sub (subject) element of the ID Token.
// The sub Claim in the UserInfo Response MUST be verified to exactly match

View File

@ -168,6 +168,37 @@ public class OidcUserServiceTests {
assertThat(userAuthority.getUserInfo()).isEqualTo(user.getUserInfo());
}
// gh-5447
@Test
public void loadUserWhenUserInfoSuccessResponseAndUserInfoSubjectIsNullThenThrowOAuth2AuthenticationException() throws Exception {
this.exception.expect(OAuth2AuthenticationException.class);
this.exception.expectMessage(containsString("invalid_user_info_response"));
MockWebServer server = new MockWebServer();
String userInfoResponse = "{\n" +
" \"email\": \"full_name@provider.com\",\n" +
" \"name\": \"full name\"\n" +
"}\n";
server.enqueue(new MockResponse()
.setHeader(HttpHeaders.ACCEPT, MediaType.APPLICATION_JSON_VALUE)
.setBody(userInfoResponse));
server.start();
String userInfoUri = server.url("/user").toString();
when(this.userInfoEndpoint.getUri()).thenReturn(userInfoUri);
when(this.userInfoEndpoint.getUserNameAttributeName()).thenReturn(StandardClaimNames.EMAIL);
when(this.accessToken.getTokenValue()).thenReturn("access-token");
try {
this.userService.loadUser(new OidcUserRequest(this.clientRegistration, this.accessToken, this.idToken));
} finally {
server.shutdown();
}
}
@Test
public void loadUserWhenUserInfoSuccessResponseAndUserInfoSubjectNotSameAsIdTokenSubjectThenThrowOAuth2AuthenticationException() throws Exception {
this.exception.expect(OAuth2AuthenticationException.class);