diff --git a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/oidc/userinfo/OidcUserRequestUtils.java b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/oidc/userinfo/OidcUserRequestUtils.java index 5f39b8d961b..01c73f9449b 100644 --- a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/oidc/userinfo/OidcUserRequestUtils.java +++ b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/oidc/userinfo/OidcUserRequestUtils.java @@ -78,13 +78,18 @@ static boolean shouldRetrieveUserInfo(OidcUserRequest userRequest) { static OidcUser getUser(OidcUserRequest userRequest, OidcUserInfo userInfo) { Set authorities = new LinkedHashSet<>(); - authorities.add(new OidcUserAuthority(userRequest.getIdToken(), userInfo)); + ClientRegistration.ProviderDetails providerDetails = userRequest.getClientRegistration().getProviderDetails(); + String userNameAttributeName = providerDetails.getUserInfoEndpoint().getUserNameAttributeName(); + if (StringUtils.hasLength(userNameAttributeName)) { + authorities.add(new OidcUserAuthority(userRequest.getIdToken(), userInfo, userNameAttributeName)); + } + else { + authorities.add(new OidcUserAuthority(userRequest.getIdToken(), userInfo)); + } OAuth2AccessToken token = userRequest.getAccessToken(); for (String scope : token.getScopes()) { authorities.add(new SimpleGrantedAuthority("SCOPE_" + scope)); } - ClientRegistration.ProviderDetails providerDetails = userRequest.getClientRegistration().getProviderDetails(); - String userNameAttributeName = providerDetails.getUserInfoEndpoint().getUserNameAttributeName(); if (StringUtils.hasText(userNameAttributeName)) { return new DefaultOidcUser(authorities, userRequest.getIdToken(), userInfo, userNameAttributeName); } diff --git a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/userinfo/DefaultOAuth2UserService.java b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/userinfo/DefaultOAuth2UserService.java index 95336084b7b..3045d22914d 100644 --- a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/userinfo/DefaultOAuth2UserService.java +++ b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/userinfo/DefaultOAuth2UserService.java @@ -95,7 +95,7 @@ public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2Authentic ResponseEntity> response = getResponse(userRequest, request); OAuth2AccessToken token = userRequest.getAccessToken(); Map attributes = this.attributesConverter.convert(userRequest).convert(response.getBody()); - Collection authorities = getAuthorities(token, attributes); + Collection authorities = getAuthorities(token, attributes, userNameAttributeName); return new DefaultOAuth2User(authorities, attributes, userNameAttributeName); } @@ -187,9 +187,10 @@ private String getUserNameAttributeName(OAuth2UserRequest userRequest) { return userNameAttributeName; } - private Collection getAuthorities(OAuth2AccessToken token, Map attributes) { + private Collection getAuthorities(OAuth2AccessToken token, Map attributes, + String userNameAttributeName) { Collection authorities = new LinkedHashSet<>(); - authorities.add(new OAuth2UserAuthority(attributes)); + authorities.add(new OAuth2UserAuthority(attributes, userNameAttributeName)); for (String authority : token.getScopes()) { authorities.add(new SimpleGrantedAuthority("SCOPE_" + authority)); } diff --git a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/userinfo/DefaultReactiveOAuth2UserService.java b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/userinfo/DefaultReactiveOAuth2UserService.java index 920119baab3..54a39a9a265 100644 --- a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/userinfo/DefaultReactiveOAuth2UserService.java +++ b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/userinfo/DefaultReactiveOAuth2UserService.java @@ -130,7 +130,7 @@ public Mono loadUser(OAuth2UserRequest userRequest) throws OAuth2Aut .bodyToMono(DefaultReactiveOAuth2UserService.STRING_OBJECT_MAP) .mapNotNull((attributes) -> this.attributesConverter.convert(userRequest).convert(attributes)); return userAttributes.map((attrs) -> { - GrantedAuthority authority = new OAuth2UserAuthority(attrs); + GrantedAuthority authority = new OAuth2UserAuthority(attrs, userNameAttributeName); Set authorities = new HashSet<>(); authorities.add(authority); OAuth2AccessToken token = userRequest.getAccessToken(); diff --git a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/jackson2/OAuth2AuthenticationTokenMixinTests.java b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/jackson2/OAuth2AuthenticationTokenMixinTests.java index e4c76b69db8..aa14f020d1c 100644 --- a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/jackson2/OAuth2AuthenticationTokenMixinTests.java +++ b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/jackson2/OAuth2AuthenticationTokenMixinTests.java @@ -247,6 +247,7 @@ private static String asJson(OAuth2UserAuthority oauth2UserAuthority) { return "{\n" + " \"@class\": \"org.springframework.security.oauth2.core.user.OAuth2UserAuthority\",\n" + " \"authority\": \"" + oauth2UserAuthority.getAuthority() + "\",\n" + + " \"userNameAttributeName\": \"username\",\n" + " \"attributes\": {\n" + " \"@class\": \"java.util.Collections$UnmodifiableMap\",\n" + " \"username\": \"user\"\n" + @@ -260,6 +261,7 @@ private static String asJson(OidcUserAuthority oidcUserAuthority) { return "{\n" + " \"@class\": \"org.springframework.security.oauth2.core.oidc.user.OidcUserAuthority\",\n" + " \"authority\": \"" + oidcUserAuthority.getAuthority() + "\",\n" + + " \"userNameAttributeName\": \"" + oidcUserAuthority.getUserNameAttributeName() + "\",\n" + " \"idToken\": " + asJson(oidcUserAuthority.getIdToken()) + ",\n" + " \"userInfo\": " + asJson(oidcUserAuthority.getUserInfo()) + "\n" + " }"; diff --git a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/oidc/userinfo/OidcReactiveOAuth2UserServiceTests.java b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/oidc/userinfo/OidcReactiveOAuth2UserServiceTests.java index ade191c6da1..172a7b3bacb 100644 --- a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/oidc/userinfo/OidcReactiveOAuth2UserServiceTests.java +++ b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/oidc/userinfo/OidcReactiveOAuth2UserServiceTests.java @@ -313,6 +313,7 @@ public void loadUserWhenTokenDoesNotContainScopesThenNoScopeAuthorities() { OAuth2UserAuthority userAuthority = (OAuth2UserAuthority) user.getAuthorities().iterator().next(); assertThat(userAuthority.getAuthority()).isEqualTo("OIDC_USER"); assertThat(userAuthority.getAttributes()).isEqualTo(user.getAttributes()); + assertThat(userAuthority.getUserNameAttributeName()).isEqualTo("id"); } @Test @@ -361,6 +362,7 @@ public void loadUserWhenNestedUserInfoSuccessThenReturnUser() throws IOException OAuth2UserAuthority userAuthority = (OAuth2UserAuthority) user.getAuthorities().iterator().next(); assertThat(userAuthority.getAuthority()).isEqualTo("OIDC_USER"); assertThat(userAuthority.getAttributes()).isEqualTo(user.getAttributes()); + assertThat(userAuthority.getUserNameAttributeName()).isEqualTo("user-name"); } } diff --git a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/oidc/userinfo/OidcUserServiceTests.java b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/oidc/userinfo/OidcUserServiceTests.java index f9982c82262..baa574fa1eb 100644 --- a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/oidc/userinfo/OidcUserServiceTests.java +++ b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/oidc/userinfo/OidcUserServiceTests.java @@ -616,6 +616,7 @@ public void loadUserWhenNestedUserInfoSuccessThenReturnUser() { OAuth2UserAuthority userAuthority = (OAuth2UserAuthority) user.getAuthorities().iterator().next(); assertThat(userAuthority.getAuthority()).isEqualTo("OIDC_USER"); assertThat(userAuthority.getAttributes()).isEqualTo(user.getAttributes()); + assertThat(userAuthority.getUserNameAttributeName()).isEqualTo("user-name"); } private MockResponse jsonResponse(String json) { diff --git a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/userinfo/DefaultOAuth2UserServiceTests.java b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/userinfo/DefaultOAuth2UserServiceTests.java index 99457d4e3e7..e210019f48e 100644 --- a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/userinfo/DefaultOAuth2UserServiceTests.java +++ b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/userinfo/DefaultOAuth2UserServiceTests.java @@ -156,6 +156,7 @@ public void loadUserWhenUserInfoSuccessResponseThenReturnUser() { OAuth2UserAuthority userAuthority = (OAuth2UserAuthority) user.getAuthorities().iterator().next(); assertThat(userAuthority.getAuthority()).isEqualTo("OAUTH2_USER"); assertThat(userAuthority.getAttributes()).isEqualTo(user.getAttributes()); + assertThat(userAuthority.getUserNameAttributeName()).isEqualTo("user-name"); } @Test @@ -196,6 +197,7 @@ public void loadUserWhenNestedUserInfoSuccessThenReturnUser() { OAuth2UserAuthority userAuthority = (OAuth2UserAuthority) user.getAuthorities().iterator().next(); assertThat(userAuthority.getAuthority()).isEqualTo("OAUTH2_USER"); assertThat(userAuthority.getAttributes()).isEqualTo(user.getAttributes()); + assertThat(userAuthority.getUserNameAttributeName()).isEqualTo("user-name"); } @Test diff --git a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/userinfo/DefaultReactiveOAuth2UserServiceTests.java b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/userinfo/DefaultReactiveOAuth2UserServiceTests.java index 68aa1a31d3a..a8614842c6d 100644 --- a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/userinfo/DefaultReactiveOAuth2UserServiceTests.java +++ b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/userinfo/DefaultReactiveOAuth2UserServiceTests.java @@ -144,6 +144,7 @@ public void loadUserWhenUserInfoSuccessResponseThenReturnUser() { OAuth2UserAuthority userAuthority = (OAuth2UserAuthority) user.getAuthorities().iterator().next(); assertThat(userAuthority.getAuthority()).isEqualTo("OAUTH2_USER"); assertThat(userAuthority.getAttributes()).isEqualTo(user.getAttributes()); + assertThat(userAuthority.getUserNameAttributeName()).isEqualTo("id"); } // gh-9336 @@ -203,6 +204,7 @@ public void loadUserWhenNestedUserInfoSuccessThenReturnUser() { OAuth2UserAuthority userAuthority = (OAuth2UserAuthority) user.getAuthorities().iterator().next(); assertThat(userAuthority.getAuthority()).isEqualTo("OAUTH2_USER"); assertThat(userAuthority.getAttributes()).isEqualTo(user.getAttributes()); + assertThat(userAuthority.getUserNameAttributeName()).isEqualTo("user-name"); } // gh-5500 diff --git a/oauth2/oauth2-core/src/main/java/org/springframework/security/oauth2/core/oidc/user/OidcUserAuthority.java b/oauth2/oauth2-core/src/main/java/org/springframework/security/oauth2/core/oidc/user/OidcUserAuthority.java index 31920772c75..84aeb34d086 100644 --- a/oauth2/oauth2-core/src/main/java/org/springframework/security/oauth2/core/oidc/user/OidcUserAuthority.java +++ b/oauth2/oauth2-core/src/main/java/org/springframework/security/oauth2/core/oidc/user/OidcUserAuthority.java @@ -20,6 +20,7 @@ import java.util.Map; import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.oauth2.core.oidc.IdTokenClaimNames; import org.springframework.security.oauth2.core.oidc.OidcIdToken; import org.springframework.security.oauth2.core.oidc.OidcUserInfo; import org.springframework.security.oauth2.core.user.OAuth2UserAuthority; @@ -57,6 +58,19 @@ public OidcUserAuthority(OidcIdToken idToken, OidcUserInfo userInfo) { this("OIDC_USER", idToken, userInfo); } + /** + * Constructs a {@code OidcUserAuthority} using the provided parameters and defaults + * {@link #getAuthority()} to {@code OIDC_USER}. + * @param idToken the {@link OidcIdToken ID Token} containing claims about the user + * @param userInfo the {@link OidcUserInfo UserInfo} containing claims about the user, + * may be {@code null} + * @param userNameAttributeName the attribute name used to access the user's name from + * the attributes + */ + public OidcUserAuthority(OidcIdToken idToken, OidcUserInfo userInfo, String userNameAttributeName) { + this("OIDC_USER", idToken, userInfo, userNameAttributeName); + } + /** * Constructs a {@code OidcUserAuthority} using the provided parameters. * @param authority the authority granted to the user @@ -65,7 +79,21 @@ public OidcUserAuthority(OidcIdToken idToken, OidcUserInfo userInfo) { * may be {@code null} */ public OidcUserAuthority(String authority, OidcIdToken idToken, OidcUserInfo userInfo) { - super(authority, collectClaims(idToken, userInfo)); + this(authority, idToken, userInfo, IdTokenClaimNames.SUB); + } + + /** + * Constructs a {@code OidcUserAuthority} using the provided parameters. + * @param authority the authority granted to the user + * @param idToken the {@link OidcIdToken ID Token} containing claims about the user + * @param userInfo the {@link OidcUserInfo UserInfo} containing claims about the user, + * may be {@code null} + * @param userNameAttributeName the attribute name used to access the user's name from + * the attributes + */ + public OidcUserAuthority(String authority, OidcIdToken idToken, OidcUserInfo userInfo, + String userNameAttributeName) { + super(authority, collectClaims(idToken, userInfo), userNameAttributeName); this.idToken = idToken; this.userInfo = userInfo; } diff --git a/oauth2/oauth2-core/src/main/java/org/springframework/security/oauth2/core/user/OAuth2UserAuthority.java b/oauth2/oauth2-core/src/main/java/org/springframework/security/oauth2/core/user/OAuth2UserAuthority.java index 0182a72e311..16249788388 100644 --- a/oauth2/oauth2-core/src/main/java/org/springframework/security/oauth2/core/user/OAuth2UserAuthority.java +++ b/oauth2/oauth2-core/src/main/java/org/springframework/security/oauth2/core/user/OAuth2UserAuthority.java @@ -22,6 +22,7 @@ import java.util.Map; import java.util.Objects; +import org.springframework.lang.Nullable; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.SpringSecurityCoreVersion; import org.springframework.util.Assert; @@ -41,6 +42,8 @@ public class OAuth2UserAuthority implements GrantedAuthority { private final Map attributes; + private final String userNameAttributeName; + /** * Constructs a {@code OAuth2UserAuthority} using the provided parameters and defaults * {@link #getAuthority()} to {@code OAUTH2_USER}. @@ -50,16 +53,39 @@ public OAuth2UserAuthority(Map attributes) { this("OAUTH2_USER", attributes); } + /** + * Constructs a {@code OAuth2UserAuthority} using the provided parameters and defaults + * {@link #getAuthority()} to {@code OAUTH2_USER}. + * @param attributes the attributes about the user + * @param userNameAttributeName the attribute name used to access the user's name from + * the attributes + */ + public OAuth2UserAuthority(Map attributes, @Nullable String userNameAttributeName) { + this("OAUTH2_USER", attributes, userNameAttributeName); + } + /** * Constructs a {@code OAuth2UserAuthority} using the provided parameters. * @param authority the authority granted to the user * @param attributes the attributes about the user */ public OAuth2UserAuthority(String authority, Map attributes) { + this(authority, attributes, null); + } + + /** + * Constructs a {@code OAuth2UserAuthority} using the provided parameters. + * @param authority the authority granted to the user + * @param attributes the attributes about the user + * @param userNameAttributeName the attribute name used to access the user's name from + * the attributes + */ + public OAuth2UserAuthority(String authority, Map attributes, String userNameAttributeName) { Assert.hasText(authority, "authority cannot be empty"); Assert.notEmpty(attributes, "attributes cannot be empty"); this.authority = authority; this.attributes = Collections.unmodifiableMap(new LinkedHashMap<>(attributes)); + this.userNameAttributeName = userNameAttributeName; } @Override @@ -75,6 +101,15 @@ public Map getAttributes() { return this.attributes; } + /** + * Returns the attribute name used to access the user's name from the attributes. + * @return the attribute name used to access the user's name from the attributes + */ + @Nullable + public String getUserNameAttributeName() { + return this.userNameAttributeName; + } + @Override public boolean equals(Object obj) { if (this == obj) { diff --git a/oauth2/oauth2-core/src/test/java/org/springframework/security/oauth2/core/user/TestOAuth2Users.java b/oauth2/oauth2-core/src/test/java/org/springframework/security/oauth2/core/user/TestOAuth2Users.java index 8c50d677b58..f55e78ef27f 100644 --- a/oauth2/oauth2-core/src/test/java/org/springframework/security/oauth2/core/user/TestOAuth2Users.java +++ b/oauth2/oauth2-core/src/test/java/org/springframework/security/oauth2/core/user/TestOAuth2Users.java @@ -37,12 +37,13 @@ public static DefaultOAuth2User create() { String nameAttributeKey = "username"; Map attributes = new HashMap<>(); attributes.put(nameAttributeKey, "user"); - Collection authorities = authorities(attributes); + Collection authorities = authorities(attributes, nameAttributeKey); return new DefaultOAuth2User(authorities, attributes, nameAttributeKey); } - private static Collection authorities(Map attributes) { - return new LinkedHashSet<>(Arrays.asList(new OAuth2UserAuthority(attributes), + private static Collection authorities(Map attributes, + String userNameAttributeName) { + return new LinkedHashSet<>(Arrays.asList(new OAuth2UserAuthority(attributes, userNameAttributeName), new SimpleGrantedAuthority("SCOPE_read"), new SimpleGrantedAuthority("SCOPE_write"))); } diff --git a/test/src/main/java/org/springframework/security/test/web/reactive/server/SecurityMockServerConfigurers.java b/test/src/main/java/org/springframework/security/test/web/reactive/server/SecurityMockServerConfigurers.java index 433a769aef3..ee3a28ea4cc 100644 --- a/test/src/main/java/org/springframework/security/test/web/reactive/server/SecurityMockServerConfigurers.java +++ b/test/src/main/java/org/springframework/security/test/web/reactive/server/SecurityMockServerConfigurers.java @@ -834,7 +834,7 @@ private ClientRegistration.Builder clientRegistrationBuilder() { private Collection defaultAuthorities() { Set authorities = new LinkedHashSet<>(); - authorities.add(new OAuth2UserAuthority(this.attributes.get())); + authorities.add(new OAuth2UserAuthority(this.attributes.get(), this.nameAttributeKey)); for (String authority : this.accessToken.getScopes()) { authorities.add(new SimpleGrantedAuthority("SCOPE_" + authority)); } diff --git a/test/src/main/java/org/springframework/security/test/web/servlet/request/SecurityMockMvcRequestPostProcessors.java b/test/src/main/java/org/springframework/security/test/web/servlet/request/SecurityMockMvcRequestPostProcessors.java index 7361045902f..aa183881130 100644 --- a/test/src/main/java/org/springframework/security/test/web/servlet/request/SecurityMockMvcRequestPostProcessors.java +++ b/test/src/main/java/org/springframework/security/test/web/servlet/request/SecurityMockMvcRequestPostProcessors.java @@ -1376,7 +1376,7 @@ private ClientRegistration.Builder clientRegistrationBuilder() { private Collection defaultAuthorities() { Set authorities = new LinkedHashSet<>(); - authorities.add(new OAuth2UserAuthority(this.attributes.get())); + authorities.add(new OAuth2UserAuthority(this.attributes.get(), this.nameAttributeKey)); for (String authority : this.accessToken.getScopes()) { authorities.add(new SimpleGrantedAuthority("SCOPE_" + authority)); }