mirror of
https://github.com/spring-projects/spring-security.git
synced 2025-03-03 11:59:08 +00:00
Allow configurable accessible scopes for UserInfo resource
Fixes gh-6886
This commit is contained in:
parent
6e76df8f1d
commit
3f2108921e
@ -62,8 +62,8 @@ public class OidcUserService implements OAuth2UserService<OidcUserRequest, OidcU
|
||||
private static final String INVALID_USER_INFO_RESPONSE_ERROR_CODE = "invalid_user_info_response";
|
||||
private static final Converter<Map<String, Object>, Map<String, Object>> DEFAULT_CLAIM_TYPE_CONVERTER =
|
||||
new ClaimTypeConverter(createDefaultClaimTypeConverters());
|
||||
private final Set<String> userInfoScopes = new HashSet<>(
|
||||
Arrays.asList(OidcScopes.PROFILE, OidcScopes.EMAIL, OidcScopes.ADDRESS, OidcScopes.PHONE));
|
||||
private Set<String> accessibleScopes = new HashSet<>(Arrays.asList(
|
||||
OidcScopes.PROFILE, OidcScopes.EMAIL, OidcScopes.ADDRESS, OidcScopes.PHONE));
|
||||
private OAuth2UserService<OAuth2UserRequest, OAuth2User> oauth2UserService = new DefaultOAuth2UserService();
|
||||
private Function<ClientRegistration, Converter<Map<String, Object>, Map<String, Object>>> claimTypeConverterFactory =
|
||||
clientRegistration -> DEFAULT_CLAIM_TYPE_CONVERTER;
|
||||
@ -160,8 +160,9 @@ public class OidcUserService implements OAuth2UserService<OidcUserRequest, OidcU
|
||||
if (AuthorizationGrantType.AUTHORIZATION_CODE.equals(
|
||||
userRequest.getClientRegistration().getAuthorizationGrantType())) {
|
||||
|
||||
// Return true if there is at least one match between the authorized scope(s) and UserInfo scope(s)
|
||||
return CollectionUtils.containsAny(userRequest.getAccessToken().getScopes(), this.userInfoScopes);
|
||||
// Return true if there is at least one match between the authorized scope(s) and accessible scope(s)
|
||||
return this.accessibleScopes.isEmpty() ||
|
||||
CollectionUtils.containsAny(userRequest.getAccessToken().getScopes(), this.accessibleScopes);
|
||||
}
|
||||
|
||||
return false;
|
||||
@ -190,4 +191,19 @@ public class OidcUserService implements OAuth2UserService<OidcUserRequest, OidcU
|
||||
Assert.notNull(claimTypeConverterFactory, "claimTypeConverterFactory cannot be null");
|
||||
this.claimTypeConverterFactory = claimTypeConverterFactory;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the scope(s) that allow access to the user info resource.
|
||||
* The default is {@link OidcScopes#PROFILE profile}, {@link OidcScopes#EMAIL email}, {@link OidcScopes#ADDRESS address} and {@link OidcScopes#PHONE phone}.
|
||||
* The scope(s) are checked against the "granted" scope(s) associated to the {@link OidcUserRequest#getAccessToken() access token}
|
||||
* to determine if the user info resource is accessible or not.
|
||||
* If there is at least one match, the user info resource will be requested, otherwise it will not.
|
||||
*
|
||||
* @since 5.2
|
||||
* @param accessibleScopes the scope(s) that allow access to the user info resource
|
||||
*/
|
||||
public final void setAccessibleScopes(Set<String> accessibleScopes) {
|
||||
Assert.notNull(accessibleScopes, "accessibleScopes cannot be null");
|
||||
this.accessibleScopes = accessibleScopes;
|
||||
}
|
||||
}
|
||||
|
@ -41,11 +41,9 @@ import org.springframework.security.oauth2.core.oidc.user.OidcUser;
|
||||
import org.springframework.security.oauth2.core.oidc.user.OidcUserAuthority;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.function.Function;
|
||||
|
||||
@ -116,6 +114,17 @@ public class OidcUserServiceTests {
|
||||
.isInstanceOf(IllegalArgumentException.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setAccessibleScopesWhenNullThenThrowIllegalArgumentException() {
|
||||
assertThatThrownBy(() -> this.userService.setAccessibleScopes(null))
|
||||
.isInstanceOf(IllegalArgumentException.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setAccessibleScopesWhenEmptyThenSet() {
|
||||
this.userService.setAccessibleScopes(Collections.emptySet());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void loadUserWhenUserRequestIsNullThenThrowIllegalArgumentException() {
|
||||
this.exception.expect(IllegalArgumentException.class);
|
||||
@ -130,20 +139,91 @@ public class OidcUserServiceTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void loadUserWhenAuthorizedScopesDoesNotContainUserInfoScopesThenUserInfoEndpointNotRequested() {
|
||||
public void loadUserWhenNonStandardScopesAuthorizedThenUserInfoEndpointNotRequested() {
|
||||
ClientRegistration clientRegistration = this.clientRegistrationBuilder
|
||||
.userInfoUri("https://provider.com/user").build();
|
||||
|
||||
Set<String> authorizedScopes = new LinkedHashSet<>(Arrays.asList("scope1", "scope2"));
|
||||
OAuth2AccessToken accessToken = new OAuth2AccessToken(
|
||||
OAuth2AccessToken.TokenType.BEARER, "access-token",
|
||||
Instant.MIN, Instant.MAX, authorizedScopes);
|
||||
this.accessToken = scopes("scope1", "scope2");
|
||||
|
||||
OidcUser user = this.userService.loadUser(
|
||||
new OidcUserRequest(clientRegistration, accessToken, this.idToken));
|
||||
new OidcUserRequest(clientRegistration, this.accessToken, this.idToken));
|
||||
assertThat(user.getUserInfo()).isNull();
|
||||
}
|
||||
|
||||
// gh-6886
|
||||
@Test
|
||||
public void loadUserWhenNonStandardScopesAuthorizedAndAccessibleScopesMatchThenUserInfoEndpointRequested() {
|
||||
String userInfoResponse = "{\n" +
|
||||
" \"sub\": \"subject1\",\n" +
|
||||
" \"name\": \"first last\",\n" +
|
||||
" \"given_name\": \"first\",\n" +
|
||||
" \"family_name\": \"last\",\n" +
|
||||
" \"preferred_username\": \"user1\",\n" +
|
||||
" \"email\": \"user1@example.com\"\n" +
|
||||
"}\n";
|
||||
this.server.enqueue(jsonResponse(userInfoResponse));
|
||||
|
||||
String userInfoUri = this.server.url("/user").toString();
|
||||
|
||||
ClientRegistration clientRegistration = this.clientRegistrationBuilder
|
||||
.userInfoUri(userInfoUri).build();
|
||||
|
||||
this.accessToken = scopes("scope1", "scope2");
|
||||
this.userService.setAccessibleScopes(Collections.singleton("scope2"));
|
||||
|
||||
OidcUser user = this.userService.loadUser(
|
||||
new OidcUserRequest(clientRegistration, this.accessToken, this.idToken));
|
||||
assertThat(user.getUserInfo()).isNotNull();
|
||||
}
|
||||
|
||||
// gh-6886
|
||||
@Test
|
||||
public void loadUserWhenNonStandardScopesAuthorizedAndAccessibleScopesEmptyThenUserInfoEndpointRequested() {
|
||||
String userInfoResponse = "{\n" +
|
||||
" \"sub\": \"subject1\",\n" +
|
||||
" \"name\": \"first last\",\n" +
|
||||
" \"given_name\": \"first\",\n" +
|
||||
" \"family_name\": \"last\",\n" +
|
||||
" \"preferred_username\": \"user1\",\n" +
|
||||
" \"email\": \"user1@example.com\"\n" +
|
||||
"}\n";
|
||||
this.server.enqueue(jsonResponse(userInfoResponse));
|
||||
|
||||
String userInfoUri = this.server.url("/user").toString();
|
||||
|
||||
ClientRegistration clientRegistration = this.clientRegistrationBuilder
|
||||
.userInfoUri(userInfoUri).build();
|
||||
|
||||
this.accessToken = scopes("scope1", "scope2");
|
||||
this.userService.setAccessibleScopes(Collections.emptySet());
|
||||
|
||||
OidcUser user = this.userService.loadUser(
|
||||
new OidcUserRequest(clientRegistration, this.accessToken, this.idToken));
|
||||
assertThat(user.getUserInfo()).isNotNull();
|
||||
}
|
||||
|
||||
// gh-6886
|
||||
@Test
|
||||
public void loadUserWhenStandardScopesAuthorizedThenUserInfoEndpointRequested() {
|
||||
String userInfoResponse = "{\n" +
|
||||
" \"sub\": \"subject1\",\n" +
|
||||
" \"name\": \"first last\",\n" +
|
||||
" \"given_name\": \"first\",\n" +
|
||||
" \"family_name\": \"last\",\n" +
|
||||
" \"preferred_username\": \"user1\",\n" +
|
||||
" \"email\": \"user1@example.com\"\n" +
|
||||
"}\n";
|
||||
this.server.enqueue(jsonResponse(userInfoResponse));
|
||||
|
||||
String userInfoUri = this.server.url("/user").toString();
|
||||
|
||||
ClientRegistration clientRegistration = this.clientRegistrationBuilder
|
||||
.userInfoUri(userInfoUri).build();
|
||||
|
||||
OidcUser user = this.userService.loadUser(
|
||||
new OidcUserRequest(clientRegistration, this.accessToken, this.idToken));
|
||||
assertThat(user.getUserInfo()).isNotNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void loadUserWhenUserInfoSuccessResponseThenReturnUser() {
|
||||
String userInfoResponse = "{\n" +
|
||||
|
Loading…
x
Reference in New Issue
Block a user