|
@@ -18,9 +18,8 @@ package org.springframework.security.oauth2.client.oidc.userinfo;
|
|
|
|
|
|
import java.time.Instant;
|
|
|
import java.util.HashMap;
|
|
|
-import java.util.HashSet;
|
|
|
import java.util.Map;
|
|
|
-import java.util.Set;
|
|
|
+import java.util.function.BiFunction;
|
|
|
import java.util.function.Function;
|
|
|
import java.util.function.Predicate;
|
|
|
|
|
@@ -28,7 +27,6 @@ import reactor.core.publisher.Mono;
|
|
|
|
|
|
import org.springframework.core.convert.TypeDescriptor;
|
|
|
import org.springframework.core.convert.converter.Converter;
|
|
|
-import org.springframework.security.core.GrantedAuthority;
|
|
|
import org.springframework.security.core.authority.SimpleGrantedAuthority;
|
|
|
import org.springframework.security.oauth2.client.registration.ClientRegistration;
|
|
|
import org.springframework.security.oauth2.client.userinfo.DefaultReactiveOAuth2UserService;
|
|
@@ -40,6 +38,7 @@ import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
|
|
|
import org.springframework.security.oauth2.core.OAuth2Error;
|
|
|
import org.springframework.security.oauth2.core.converter.ClaimConversionService;
|
|
|
import org.springframework.security.oauth2.core.converter.ClaimTypeConverter;
|
|
|
+import org.springframework.security.oauth2.core.oidc.OidcIdToken;
|
|
|
import org.springframework.security.oauth2.core.oidc.OidcUserInfo;
|
|
|
import org.springframework.security.oauth2.core.oidc.StandardClaimNames;
|
|
|
import org.springframework.security.oauth2.core.oidc.user.DefaultOidcUser;
|
|
@@ -47,7 +46,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.user.OAuth2User;
|
|
|
import org.springframework.util.Assert;
|
|
|
-import org.springframework.util.StringUtils;
|
|
|
|
|
|
/**
|
|
|
* An implementation of an {@link ReactiveOAuth2UserService} that supports OpenID Connect
|
|
@@ -75,6 +73,8 @@ public class OidcReactiveOAuth2UserService implements ReactiveOAuth2UserService<
|
|
|
|
|
|
private Predicate<OidcUserRequest> retrieveUserInfo = OidcUserRequestUtils::shouldRetrieveUserInfo;
|
|
|
|
|
|
+ private BiFunction<OidcUserRequest, OidcUserInfo, Mono<OidcUser>> oidcUserMapper = this::getUser;
|
|
|
+
|
|
|
/**
|
|
|
* Returns the default {@link Converter}'s used for type conversion of claim values
|
|
|
* for an {@link OidcUserInfo}.
|
|
@@ -103,29 +103,15 @@ public class OidcReactiveOAuth2UserService implements ReactiveOAuth2UserService<
|
|
|
Assert.notNull(userRequest, "userRequest cannot be null");
|
|
|
// @formatter:off
|
|
|
return getUserInfo(userRequest)
|
|
|
- .map((userInfo) ->
|
|
|
- new OidcUserAuthority(userRequest.getIdToken(), userInfo)
|
|
|
- )
|
|
|
- .defaultIfEmpty(new OidcUserAuthority(userRequest.getIdToken(), null))
|
|
|
- .map((authority) -> {
|
|
|
- OidcUserInfo userInfo = authority.getUserInfo();
|
|
|
- Set<GrantedAuthority> authorities = new HashSet<>();
|
|
|
- authorities.add(authority);
|
|
|
- OAuth2AccessToken token = userRequest.getAccessToken();
|
|
|
- for (String scope : token.getScopes()) {
|
|
|
- authorities.add(new SimpleGrantedAuthority("SCOPE_" + scope));
|
|
|
- }
|
|
|
- String userNameAttributeName = userRequest.getClientRegistration().getProviderDetails()
|
|
|
- .getUserInfoEndpoint().getUserNameAttributeName();
|
|
|
- if (StringUtils.hasText(userNameAttributeName)) {
|
|
|
- return new DefaultOidcUser(authorities, userRequest.getIdToken(), userInfo,
|
|
|
- userNameAttributeName);
|
|
|
- }
|
|
|
- return new DefaultOidcUser(authorities, userRequest.getIdToken(), userInfo);
|
|
|
- });
|
|
|
+ .flatMap((userInfo) -> this.oidcUserMapper.apply(userRequest, userInfo))
|
|
|
+ .switchIfEmpty(Mono.defer(() -> this.oidcUserMapper.apply(userRequest, null)));
|
|
|
// @formatter:on
|
|
|
}
|
|
|
|
|
|
+ private Mono<OidcUser> getUser(OidcUserRequest userRequest, OidcUserInfo userInfo) {
|
|
|
+ return Mono.just(OidcUserRequestUtils.getUser(userRequest, userInfo));
|
|
|
+ }
|
|
|
+
|
|
|
private Mono<OidcUserInfo> getUserInfo(OidcUserRequest userRequest) {
|
|
|
if (!this.retrieveUserInfo.test(userRequest)) {
|
|
|
return Mono.empty();
|
|
@@ -193,4 +179,60 @@ public class OidcReactiveOAuth2UserService implements ReactiveOAuth2UserService<
|
|
|
this.retrieveUserInfo = retrieveUserInfo;
|
|
|
}
|
|
|
|
|
|
+ /**
|
|
|
+ * Sets the {@code BiFunction} used to map the {@link OidcUser user} from the
|
|
|
+ * {@link OidcUserRequest user request} and {@link OidcUserInfo user info}.
|
|
|
+ * <p>
|
|
|
+ * This is useful when you need to map the user or authorities from the access token
|
|
|
+ * itself. For example, when the authorization server provides authorization
|
|
|
+ * information in the access token payload you can do the following: <pre>
|
|
|
+ * @Bean
|
|
|
+ * public OidcReactiveOAuth2UserService oidcUserService() {
|
|
|
+ * var userService = new OidcReactiveOAuth2UserService();
|
|
|
+ * userService.setOidcUserMapper(oidcUserMapper());
|
|
|
+ * return userService;
|
|
|
+ * }
|
|
|
+ *
|
|
|
+ * private static BiFunction<OidcUserRequest, OidcUserInfo, Mono<OidcUser>> oidcUserMapper() {
|
|
|
+ * return (userRequest, userInfo) -> {
|
|
|
+ * var accessToken = userRequest.getAccessToken();
|
|
|
+ * var grantedAuthorities = new HashSet<GrantedAuthority>();
|
|
|
+ * // TODO: Map authorities from the access token
|
|
|
+ * var userNameAttributeName = "preferred_username";
|
|
|
+ * return Mono.just(new DefaultOidcUser(
|
|
|
+ * grantedAuthorities,
|
|
|
+ * userRequest.getIdToken(),
|
|
|
+ * userInfo,
|
|
|
+ * userNameAttributeName
|
|
|
+ * ));
|
|
|
+ * };
|
|
|
+ * }
|
|
|
+ * </pre>
|
|
|
+ * <p>
|
|
|
+ * Note that you can access the {@code userNameAttributeName} via the
|
|
|
+ * {@link ClientRegistration} as follows: <pre>
|
|
|
+ * var userNameAttributeName = userRequest.getClientRegistration()
|
|
|
+ * .getProviderDetails()
|
|
|
+ * .getUserInfoEndpoint()
|
|
|
+ * .getUserNameAttributeName();
|
|
|
+ * </pre>
|
|
|
+ * <p>
|
|
|
+ * By default, a {@link DefaultOidcUser} is created with authorities mapped as
|
|
|
+ * follows:
|
|
|
+ * <ul>
|
|
|
+ * <li>An {@link OidcUserAuthority} is created from the {@link OidcIdToken} and
|
|
|
+ * {@link OidcUserInfo} with an authority of {@code OIDC_USER}</li>
|
|
|
+ * <li>Additional {@link SimpleGrantedAuthority authorities} are mapped from the
|
|
|
+ * {@link OAuth2AccessToken#getScopes() access token scopes} with a prefix of
|
|
|
+ * {@code SCOPE_}</li>
|
|
|
+ * </ul>
|
|
|
+ * @param oidcUserMapper the function used to map the {@link OidcUser} from the
|
|
|
+ * {@link OidcUserRequest} and {@link OidcUserInfo}
|
|
|
+ * @since 6.3
|
|
|
+ */
|
|
|
+ public final void setOidcUserMapper(BiFunction<OidcUserRequest, OidcUserInfo, Mono<OidcUser>> oidcUserMapper) {
|
|
|
+ Assert.notNull(oidcUserMapper, "oidcUserMapper cannot be null");
|
|
|
+ this.oidcUserMapper = oidcUserMapper;
|
|
|
+ }
|
|
|
+
|
|
|
}
|