Browse Source

Align setRetrieveUserInfo() between OidcUserService and OidcReactiveOAuth2UserService

Closes gh-18057
pull/18064/head
Joe Grandja 2 months ago
parent
commit
af1de950ae
  1. 9
      oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/oidc/userinfo/OidcReactiveOAuth2UserService.java
  2. 31
      oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/oidc/userinfo/OidcUserRequestUtils.java
  3. 15
      oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/oidc/userinfo/OidcUserService.java
  4. 19
      oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/oidc/userinfo/OidcReactiveOAuth2UserServiceTests.java
  5. 8
      oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/oidc/userinfo/OidcUserRequestUtilsTests.java

9
oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/oidc/userinfo/OidcReactiveOAuth2UserService.java

@ -156,15 +156,14 @@ public class OidcReactiveOAuth2UserService implements ReactiveOAuth2UserService<
* Sets the {@code Predicate} used to determine if the UserInfo Endpoint should be * Sets the {@code Predicate} used to determine if the UserInfo Endpoint should be
* called to retrieve information about the End-User (Resource Owner). * called to retrieve information about the End-User (Resource Owner).
* <p> * <p>
* By default, the UserInfo Endpoint is called if all of the following are true: * By default, the UserInfo Endpoint is called if all the following are true:
* <ul> * <ul>
* <li>The user info endpoint is defined on the ClientRegistration</li> * <li>The user info endpoint is defined on the ClientRegistration</li>
* <li>The Client Registration uses the * <li>The Client Registration uses the
* {@link AuthorizationGrantType#AUTHORIZATION_CODE} and scopes in the access token * {@link AuthorizationGrantType#AUTHORIZATION_CODE}</li>
* are defined in the {@link ClientRegistration}</li>
* </ul> * </ul>
* @param retrieveUserInfo the function used to determine if the UserInfo Endpoint * @param retrieveUserInfo the {@code Predicate} used to determine if the UserInfo
* should be called * Endpoint should be called
* @since 6.3 * @since 6.3
*/ */
public final void setRetrieveUserInfo(Predicate<OidcUserRequest> retrieveUserInfo) { public final void setRetrieveUserInfo(Predicate<OidcUserRequest> retrieveUserInfo) {

31
oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/oidc/userinfo/OidcUserRequestUtils.java

@ -28,7 +28,6 @@ import org.springframework.security.oauth2.core.oidc.OidcUserInfo;
import org.springframework.security.oauth2.core.oidc.user.DefaultOidcUser; import org.springframework.security.oauth2.core.oidc.user.DefaultOidcUser;
import org.springframework.security.oauth2.core.oidc.user.OidcUser; import org.springframework.security.oauth2.core.oidc.user.OidcUser;
import org.springframework.security.oauth2.core.oidc.user.OidcUserAuthority; import org.springframework.security.oauth2.core.oidc.user.OidcUserAuthority;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils; import org.springframework.util.StringUtils;
/** /**
@ -40,38 +39,24 @@ import org.springframework.util.StringUtils;
final class OidcUserRequestUtils { final class OidcUserRequestUtils {
/** /**
* Determines if an {@link OidcUserRequest} should attempt to retrieve the user info * Determines if an {@link OidcUserRequest} should attempt to retrieve the user info.
* endpoint. Will return true if all of the following are true: * Will return true if all the following are true:
* *
* <ul> * <ul>
* <li>The user info endpoint is defined on the ClientRegistration</li> * <li>The user info endpoint is defined on the ClientRegistration</li>
* <li>The Client Registration uses the * <li>The Client Registration uses the
* {@link AuthorizationGrantType#AUTHORIZATION_CODE} and scopes in the access token * {@link AuthorizationGrantType#AUTHORIZATION_CODE}</li>
* are defined in the {@link ClientRegistration}</li>
* </ul> * </ul>
* @param userRequest * @param userRequest
* @return * @return
*/ */
static boolean shouldRetrieveUserInfo(OidcUserRequest userRequest) { static boolean shouldRetrieveUserInfo(OidcUserRequest userRequest) {
// Auto-disabled if UserInfo Endpoint URI is not provided // Auto-disabled if UserInfo Endpoint URI is not provided
ClientRegistration clientRegistration = userRequest.getClientRegistration(); ClientRegistration.ProviderDetails providerDetails = userRequest.getClientRegistration().getProviderDetails();
if (!StringUtils.hasLength(clientRegistration.getProviderDetails().getUserInfoEndpoint().getUri())) { if (StringUtils.hasLength(providerDetails.getUserInfoEndpoint().getUri())
return false; && AuthorizationGrantType.AUTHORIZATION_CODE
} .equals(userRequest.getClientRegistration().getAuthorizationGrantType())) {
// The Claims requested by the profile, email, address, and phone scope values return true;
// are returned from the UserInfo Endpoint (as described in Section 5.3.2),
// when a response_type value is used that results in an Access Token being
// issued.
// However, when no Access Token is issued, which is the case for the
// response_type=id_token,
// the resulting Claims are returned in the ID Token.
// The Authorization Code Grant Flow, which is response_type=code, results in an
// Access Token being issued.
if (AuthorizationGrantType.AUTHORIZATION_CODE.equals(clientRegistration.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(),
userRequest.getClientRegistration().getScopes());
} }
return false; return false;
} }

15
oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/oidc/userinfo/OidcUserService.java

@ -27,7 +27,6 @@ import org.springframework.core.convert.TypeDescriptor;
import org.springframework.core.convert.converter.Converter; import org.springframework.core.convert.converter.Converter;
import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.oauth2.client.registration.ClientRegistration; import org.springframework.security.oauth2.client.registration.ClientRegistration;
import org.springframework.security.oauth2.client.registration.ClientRegistration.ProviderDetails;
import org.springframework.security.oauth2.client.userinfo.DefaultOAuth2UserService; import org.springframework.security.oauth2.client.userinfo.DefaultOAuth2UserService;
import org.springframework.security.oauth2.client.userinfo.OAuth2UserRequest; import org.springframework.security.oauth2.client.userinfo.OAuth2UserRequest;
import org.springframework.security.oauth2.client.userinfo.OAuth2UserService; import org.springframework.security.oauth2.client.userinfo.OAuth2UserService;
@ -45,7 +44,6 @@ import org.springframework.security.oauth2.core.oidc.user.OidcUser;
import org.springframework.security.oauth2.core.oidc.user.OidcUserAuthority; import org.springframework.security.oauth2.core.oidc.user.OidcUserAuthority;
import org.springframework.security.oauth2.core.user.OAuth2User; import org.springframework.security.oauth2.core.user.OAuth2User;
import org.springframework.util.Assert; import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
/** /**
* An implementation of an {@link OAuth2UserService} that supports OpenID Connect 1.0 * An implementation of an {@link OAuth2UserService} that supports OpenID Connect 1.0
@ -72,7 +70,7 @@ public class OidcUserService implements OAuth2UserService<OidcUserRequest, OidcU
private Function<ClientRegistration, Converter<Map<String, Object>, Map<String, Object>>> claimTypeConverterFactory = ( private Function<ClientRegistration, Converter<Map<String, Object>, Map<String, Object>>> claimTypeConverterFactory = (
clientRegistration) -> DEFAULT_CLAIM_TYPE_CONVERTER; clientRegistration) -> DEFAULT_CLAIM_TYPE_CONVERTER;
private Predicate<OidcUserRequest> retrieveUserInfo = this::shouldRetrieveUserInfo; private Predicate<OidcUserRequest> retrieveUserInfo = OidcUserRequestUtils::shouldRetrieveUserInfo;
private Converter<OidcUserSource, OidcUser> oidcUserConverter = OidcUserRequestUtils::getUser; private Converter<OidcUserSource, OidcUser> oidcUserConverter = OidcUserRequestUtils::getUser;
@ -139,17 +137,6 @@ public class OidcUserService implements OAuth2UserService<OidcUserRequest, OidcU
return DEFAULT_CLAIM_TYPE_CONVERTER.convert(oauth2User.getAttributes()); return DEFAULT_CLAIM_TYPE_CONVERTER.convert(oauth2User.getAttributes());
} }
private boolean shouldRetrieveUserInfo(OidcUserRequest userRequest) {
// Auto-disabled if UserInfo Endpoint URI is not provided
ProviderDetails providerDetails = userRequest.getClientRegistration().getProviderDetails();
if (StringUtils.hasLength(providerDetails.getUserInfoEndpoint().getUri())
&& AuthorizationGrantType.AUTHORIZATION_CODE
.equals(userRequest.getClientRegistration().getAuthorizationGrantType())) {
return true;
}
return false;
}
/** /**
* Sets the {@link OAuth2UserService} used when requesting the user info resource. * Sets the {@link OAuth2UserService} used when requesting the user info resource.
* @param oauth2UserService the {@link OAuth2UserService} used when requesting the * @param oauth2UserService the {@link OAuth2UserService} used when requesting the

19
oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/oidc/userinfo/OidcReactiveOAuth2UserServiceTests.java

@ -199,22 +199,6 @@ public class OidcReactiveOAuth2UserServiceTests {
verify(customClaimTypeConverterFactory).apply(same(userRequest.getClientRegistration())); verify(customClaimTypeConverterFactory).apply(same(userRequest.getClientRegistration()));
} }
@Test
public void loadUserWhenTokenScopesIsEmptyThenUserInfoNotRetrieved() {
// @formatter:off
OAuth2AccessToken accessToken = new OAuth2AccessToken(
this.accessToken.getTokenType(),
this.accessToken.getTokenValue(),
this.accessToken.getIssuedAt(),
this.accessToken.getExpiresAt(),
Collections.emptySet());
// @formatter:on
OidcUserRequest userRequest = new OidcUserRequest(this.registration.build(), accessToken, this.idToken);
OidcUser oidcUser = this.userService.loadUser(userRequest).block();
assertThat(oidcUser).isNotNull();
assertThat(oidcUser.getUserInfo()).isNull();
}
@Test @Test
public void loadUserWhenCustomRetrieveUserInfoSetThenUsed() { public void loadUserWhenCustomRetrieveUserInfoSetThenUsed() {
Map<String, Object> attributes = new HashMap<>(); Map<String, Object> attributes = new HashMap<>();
@ -281,6 +265,7 @@ public class OidcReactiveOAuth2UserServiceTests {
IdTokenClaimNames.SUB); IdTokenClaimNames.SUB);
given(customOidcUserMapper.apply(any(OidcUserRequest.class), isNull())).willReturn(Mono.just(actualUser)); given(customOidcUserMapper.apply(any(OidcUserRequest.class), isNull())).willReturn(Mono.just(actualUser));
this.userService.setOidcUserMapper(customOidcUserMapper); this.userService.setOidcUserMapper(customOidcUserMapper);
this.userService.setRetrieveUserInfo((oidcUserRequest) -> false);
OidcUserRequest userRequest = userRequest(); OidcUserRequest userRequest = userRequest();
OidcUser oidcUser = this.userService.loadUser(userRequest).block(); OidcUser oidcUser = this.userService.loadUser(userRequest).block();
assertThat(oidcUser).isNotNull(); assertThat(oidcUser).isNotNull();
@ -291,6 +276,7 @@ public class OidcReactiveOAuth2UserServiceTests {
@Test @Test
public void loadUserWhenTokenContainsScopesThenIndividualScopeAuthorities() { public void loadUserWhenTokenContainsScopesThenIndividualScopeAuthorities() {
OidcReactiveOAuth2UserService userService = new OidcReactiveOAuth2UserService(); OidcReactiveOAuth2UserService userService = new OidcReactiveOAuth2UserService();
userService.setRetrieveUserInfo((oidcUserRequest) -> false);
OidcUserRequest request = new OidcUserRequest(TestClientRegistrations.clientRegistration().build(), OidcUserRequest request = new OidcUserRequest(TestClientRegistrations.clientRegistration().build(),
TestOAuth2AccessTokens.scopes("message:read", "message:write"), TestOidcIdTokens.idToken().build()); TestOAuth2AccessTokens.scopes("message:read", "message:write"), TestOidcIdTokens.idToken().build());
OidcUser user = userService.loadUser(request).block(); OidcUser user = userService.loadUser(request).block();
@ -304,6 +290,7 @@ public class OidcReactiveOAuth2UserServiceTests {
@Test @Test
public void loadUserWhenTokenDoesNotContainScopesThenNoScopeAuthorities() { public void loadUserWhenTokenDoesNotContainScopesThenNoScopeAuthorities() {
OidcReactiveOAuth2UserService userService = new OidcReactiveOAuth2UserService(); OidcReactiveOAuth2UserService userService = new OidcReactiveOAuth2UserService();
userService.setRetrieveUserInfo((oidcUserRequest) -> false);
OidcUserRequest request = new OidcUserRequest(TestClientRegistrations.clientRegistration().build(), OidcUserRequest request = new OidcUserRequest(TestClientRegistrations.clientRegistration().build(),
TestOAuth2AccessTokens.noScopes(), TestOidcIdTokens.idToken().build()); TestOAuth2AccessTokens.noScopes(), TestOidcIdTokens.idToken().build());
OidcUser user = userService.loadUser(request).block(); OidcUser user = userService.loadUser(request).block();

8
oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/oidc/userinfo/OidcUserRequestUtilsTests.java

@ -45,7 +45,7 @@ public class OidcUserRequestUtilsTests {
Instant.now().plus(Duration.ofDays(1)), Collections.singleton("read:user")); Instant.now().plus(Duration.ofDays(1)), Collections.singleton("read:user"));
@Test @Test
public void shouldRetrieveUserInfoWhenEndpointDefinedAndScopesOverlapThenTrue() { public void shouldRetrieveUserInfoWhenUserInfoUriThenTrue() {
assertThat(OidcUserRequestUtils.shouldRetrieveUserInfo(userRequest())).isTrue(); assertThat(OidcUserRequestUtils.shouldRetrieveUserInfo(userRequest())).isTrue();
} }
@ -55,12 +55,6 @@ public class OidcUserRequestUtilsTests {
assertThat(OidcUserRequestUtils.shouldRetrieveUserInfo(userRequest())).isFalse(); assertThat(OidcUserRequestUtils.shouldRetrieveUserInfo(userRequest())).isFalse();
} }
@Test
public void shouldRetrieveUserInfoWhenDifferentScopesThenFalse() {
this.registration.scope("notintoken");
assertThat(OidcUserRequestUtils.shouldRetrieveUserInfo(userRequest())).isFalse();
}
@Test @Test
public void shouldRetrieveUserInfoWhenNotAuthorizationCodeThenFalse() { public void shouldRetrieveUserInfoWhenNotAuthorizationCodeThenFalse() {
this.registration.authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS); this.registration.authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS);

Loading…
Cancel
Save