|
|
|
@ -18,9 +18,8 @@ package org.springframework.security.oauth2.client.oidc.userinfo; |
|
|
|
|
|
|
|
|
|
|
|
import java.time.Instant; |
|
|
|
import java.time.Instant; |
|
|
|
import java.util.HashMap; |
|
|
|
import java.util.HashMap; |
|
|
|
import java.util.HashSet; |
|
|
|
|
|
|
|
import java.util.Map; |
|
|
|
import java.util.Map; |
|
|
|
import java.util.Set; |
|
|
|
import java.util.function.BiFunction; |
|
|
|
import java.util.function.Function; |
|
|
|
import java.util.function.Function; |
|
|
|
import java.util.function.Predicate; |
|
|
|
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.TypeDescriptor; |
|
|
|
import org.springframework.core.convert.converter.Converter; |
|
|
|
import org.springframework.core.convert.converter.Converter; |
|
|
|
import org.springframework.security.core.GrantedAuthority; |
|
|
|
|
|
|
|
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.userinfo.DefaultReactiveOAuth2UserService; |
|
|
|
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.OAuth2Error; |
|
|
|
import org.springframework.security.oauth2.core.converter.ClaimConversionService; |
|
|
|
import org.springframework.security.oauth2.core.converter.ClaimConversionService; |
|
|
|
import org.springframework.security.oauth2.core.converter.ClaimTypeConverter; |
|
|
|
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.OidcUserInfo; |
|
|
|
import org.springframework.security.oauth2.core.oidc.StandardClaimNames; |
|
|
|
import org.springframework.security.oauth2.core.oidc.StandardClaimNames; |
|
|
|
import org.springframework.security.oauth2.core.oidc.user.DefaultOidcUser; |
|
|
|
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.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 ReactiveOAuth2UserService} that supports OpenID Connect |
|
|
|
* 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 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 |
|
|
|
* Returns the default {@link Converter}'s used for type conversion of claim values |
|
|
|
* for an {@link OidcUserInfo}. |
|
|
|
* for an {@link OidcUserInfo}. |
|
|
|
@ -103,29 +103,15 @@ public class OidcReactiveOAuth2UserService implements ReactiveOAuth2UserService< |
|
|
|
Assert.notNull(userRequest, "userRequest cannot be null"); |
|
|
|
Assert.notNull(userRequest, "userRequest cannot be null"); |
|
|
|
// @formatter:off
|
|
|
|
// @formatter:off
|
|
|
|
return getUserInfo(userRequest) |
|
|
|
return getUserInfo(userRequest) |
|
|
|
.map((userInfo) -> |
|
|
|
.flatMap((userInfo) -> this.oidcUserMapper.apply(userRequest, userInfo)) |
|
|
|
new OidcUserAuthority(userRequest.getIdToken(), userInfo) |
|
|
|
.switchIfEmpty(Mono.defer(() -> this.oidcUserMapper.apply(userRequest, null))); |
|
|
|
) |
|
|
|
|
|
|
|
.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); |
|
|
|
|
|
|
|
}); |
|
|
|
|
|
|
|
// @formatter:on
|
|
|
|
// @formatter:on
|
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private Mono<OidcUser> getUser(OidcUserRequest userRequest, OidcUserInfo userInfo) { |
|
|
|
|
|
|
|
return Mono.just(OidcUserRequestUtils.getUser(userRequest, userInfo)); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
private Mono<OidcUserInfo> getUserInfo(OidcUserRequest userRequest) { |
|
|
|
private Mono<OidcUserInfo> getUserInfo(OidcUserRequest userRequest) { |
|
|
|
if (!this.retrieveUserInfo.test(userRequest)) { |
|
|
|
if (!this.retrieveUserInfo.test(userRequest)) { |
|
|
|
return Mono.empty(); |
|
|
|
return Mono.empty(); |
|
|
|
@ -193,4 +179,60 @@ public class OidcReactiveOAuth2UserService implements ReactiveOAuth2UserService< |
|
|
|
this.retrieveUserInfo = retrieveUserInfo; |
|
|
|
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; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
|