Expose user name attribute name in `OAuth2UserAuthority`

This commit is contained in:
Filip Hrisafov 2024-05-06 12:32:04 +02:00 committed by Steve Riesenberg
parent b41ec0ae4b
commit 99aee99b34
No known key found for this signature in database
GPG Key ID: 3D0169B18AB8F0A9
13 changed files with 92 additions and 13 deletions

View File

@ -78,13 +78,18 @@ final class OidcUserRequestUtils {
static OidcUser getUser(OidcUserRequest userRequest, OidcUserInfo userInfo) {
Set<GrantedAuthority> 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);
}

View File

@ -95,7 +95,7 @@ public class DefaultOAuth2UserService implements OAuth2UserService<OAuth2UserReq
ResponseEntity<Map<String, Object>> response = getResponse(userRequest, request);
OAuth2AccessToken token = userRequest.getAccessToken();
Map<String, Object> attributes = this.attributesConverter.convert(userRequest).convert(response.getBody());
Collection<GrantedAuthority> authorities = getAuthorities(token, attributes);
Collection<GrantedAuthority> authorities = getAuthorities(token, attributes, userNameAttributeName);
return new DefaultOAuth2User(authorities, attributes, userNameAttributeName);
}
@ -187,9 +187,10 @@ public class DefaultOAuth2UserService implements OAuth2UserService<OAuth2UserReq
return userNameAttributeName;
}
private Collection<GrantedAuthority> getAuthorities(OAuth2AccessToken token, Map<String, Object> attributes) {
private Collection<GrantedAuthority> getAuthorities(OAuth2AccessToken token, Map<String, Object> attributes,
String userNameAttributeName) {
Collection<GrantedAuthority> 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));
}

View File

@ -130,7 +130,7 @@ public class DefaultReactiveOAuth2UserService implements ReactiveOAuth2UserServi
.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<GrantedAuthority> authorities = new HashSet<>();
authorities.add(authority);
OAuth2AccessToken token = userRequest.getAccessToken();

View File

@ -247,6 +247,7 @@ public class OAuth2AuthenticationTokenMixinTests {
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 @@ public class OAuth2AuthenticationTokenMixinTests {
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" +
" }";

View File

@ -313,6 +313,7 @@ public class OidcReactiveOAuth2UserServiceTests {
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 class OidcReactiveOAuth2UserServiceTests {
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");
}
}

View File

@ -616,6 +616,7 @@ public class OidcUserServiceTests {
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) {

View File

@ -156,6 +156,7 @@ public class DefaultOAuth2UserServiceTests {
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 class DefaultOAuth2UserServiceTests {
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

View File

@ -144,6 +144,7 @@ public class DefaultReactiveOAuth2UserServiceTests {
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 class DefaultReactiveOAuth2UserServiceTests {
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

View File

@ -20,6 +20,7 @@ import java.util.HashMap;
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 class OidcUserAuthority extends OAuth2UserAuthority {
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 class OidcUserAuthority extends OAuth2UserAuthority {
* 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;
}

View File

@ -22,6 +22,7 @@ import java.util.LinkedHashMap;
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<String, Object> 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 class OAuth2UserAuthority implements GrantedAuthority {
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<String, Object> 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<String, Object> 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<String, Object> 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 class OAuth2UserAuthority implements GrantedAuthority {
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) {

View File

@ -37,12 +37,13 @@ public final class TestOAuth2Users {
String nameAttributeKey = "username";
Map<String, Object> attributes = new HashMap<>();
attributes.put(nameAttributeKey, "user");
Collection<GrantedAuthority> authorities = authorities(attributes);
Collection<GrantedAuthority> authorities = authorities(attributes, nameAttributeKey);
return new DefaultOAuth2User(authorities, attributes, nameAttributeKey);
}
private static Collection<GrantedAuthority> authorities(Map<String, Object> attributes) {
return new LinkedHashSet<>(Arrays.asList(new OAuth2UserAuthority(attributes),
private static Collection<GrantedAuthority> authorities(Map<String, Object> attributes,
String userNameAttributeName) {
return new LinkedHashSet<>(Arrays.asList(new OAuth2UserAuthority(attributes, userNameAttributeName),
new SimpleGrantedAuthority("SCOPE_read"), new SimpleGrantedAuthority("SCOPE_write")));
}

View File

@ -834,7 +834,7 @@ public final class SecurityMockServerConfigurers {
private Collection<GrantedAuthority> defaultAuthorities() {
Set<GrantedAuthority> 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));
}

View File

@ -1376,7 +1376,7 @@ public final class SecurityMockMvcRequestPostProcessors {
private Collection<GrantedAuthority> defaultAuthorities() {
Set<GrantedAuthority> 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));
}