Externalize coercion in ClaimAccessor
Fixes gh-6245
This commit is contained in:
+61
-2
@@ -15,11 +15,17 @@
|
||||
*/
|
||||
package org.springframework.security.oauth2.client.oidc.authentication;
|
||||
|
||||
import org.springframework.core.convert.TypeDescriptor;
|
||||
import org.springframework.core.convert.converter.Converter;
|
||||
import org.springframework.security.oauth2.client.registration.ClientRegistration;
|
||||
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
|
||||
import org.springframework.security.oauth2.core.OAuth2Error;
|
||||
import org.springframework.security.oauth2.core.OAuth2TokenValidator;
|
||||
import org.springframework.security.oauth2.core.converter.ClaimConversionService;
|
||||
import org.springframework.security.oauth2.core.converter.ClaimTypeConverter;
|
||||
import org.springframework.security.oauth2.core.oidc.IdTokenClaimNames;
|
||||
import org.springframework.security.oauth2.core.oidc.OidcIdToken;
|
||||
import org.springframework.security.oauth2.core.oidc.StandardClaimNames;
|
||||
import org.springframework.security.oauth2.jose.jws.JwsAlgorithm;
|
||||
import org.springframework.security.oauth2.jose.jws.MacAlgorithm;
|
||||
import org.springframework.security.oauth2.jose.jws.SignatureAlgorithm;
|
||||
@@ -31,7 +37,10 @@ import org.springframework.util.Assert;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
import java.net.URL;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.time.Instant;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
@@ -61,17 +70,55 @@ public final class OidcIdTokenDecoderFactory implements JwtDecoderFactory<Client
|
||||
put(MacAlgorithm.HS512, "HmacSHA512");
|
||||
}
|
||||
};
|
||||
private static final Converter<Map<String, Object>, Map<String, Object>> DEFAULT_CLAIM_TYPE_CONVERTER =
|
||||
new ClaimTypeConverter(createDefaultClaimTypeConverters());
|
||||
private final Map<String, JwtDecoder> jwtDecoders = new ConcurrentHashMap<>();
|
||||
private Function<ClientRegistration, OAuth2TokenValidator<Jwt>> jwtValidatorFactory = OidcIdTokenValidator::new;
|
||||
private Function<ClientRegistration, JwsAlgorithm> jwsAlgorithmResolver = clientRegistration -> SignatureAlgorithm.RS256;
|
||||
private Function<ClientRegistration, Converter<Map<String, Object>, Map<String, Object>>> claimTypeConverterFactory =
|
||||
clientRegistration -> DEFAULT_CLAIM_TYPE_CONVERTER;
|
||||
|
||||
/**
|
||||
* Returns the default {@link Converter}'s used for type conversion of claim values for an {@link OidcIdToken}.
|
||||
*
|
||||
* @return a {@link Map} of {@link Converter}'s keyed by {@link IdTokenClaimNames claim name}
|
||||
*/
|
||||
public static Map<String, Converter<Object, ?>> createDefaultClaimTypeConverters() {
|
||||
Converter<Object, ?> booleanConverter = getConverter(TypeDescriptor.valueOf(Boolean.class));
|
||||
Converter<Object, ?> instantConverter = getConverter(TypeDescriptor.valueOf(Instant.class));
|
||||
Converter<Object, ?> urlConverter = getConverter(TypeDescriptor.valueOf(URL.class));
|
||||
Converter<Object, ?> collectionStringConverter = getConverter(
|
||||
TypeDescriptor.collection(Collection.class, TypeDescriptor.valueOf(String.class)));
|
||||
|
||||
Map<String, Converter<Object, ?>> claimTypeConverters = new HashMap<>();
|
||||
claimTypeConverters.put(IdTokenClaimNames.ISS, urlConverter);
|
||||
claimTypeConverters.put(IdTokenClaimNames.AUD, collectionStringConverter);
|
||||
claimTypeConverters.put(IdTokenClaimNames.EXP, instantConverter);
|
||||
claimTypeConverters.put(IdTokenClaimNames.IAT, instantConverter);
|
||||
claimTypeConverters.put(IdTokenClaimNames.AUTH_TIME, instantConverter);
|
||||
claimTypeConverters.put(IdTokenClaimNames.AMR, collectionStringConverter);
|
||||
claimTypeConverters.put(StandardClaimNames.EMAIL_VERIFIED, booleanConverter);
|
||||
claimTypeConverters.put(StandardClaimNames.PHONE_NUMBER_VERIFIED, booleanConverter);
|
||||
claimTypeConverters.put(StandardClaimNames.UPDATED_AT, instantConverter);
|
||||
return claimTypeConverters;
|
||||
}
|
||||
|
||||
private static Converter<Object, ?> getConverter(TypeDescriptor targetDescriptor) {
|
||||
final TypeDescriptor sourceDescriptor = TypeDescriptor.valueOf(Object.class);
|
||||
return source -> ClaimConversionService.getSharedInstance().convert(source, sourceDescriptor, targetDescriptor);
|
||||
}
|
||||
|
||||
@Override
|
||||
public JwtDecoder createDecoder(ClientRegistration clientRegistration) {
|
||||
Assert.notNull(clientRegistration, "clientRegistration cannot be null");
|
||||
return this.jwtDecoders.computeIfAbsent(clientRegistration.getRegistrationId(), key -> {
|
||||
NimbusJwtDecoder jwtDecoder = buildDecoder(clientRegistration);
|
||||
OAuth2TokenValidator<Jwt> jwtValidator = this.jwtValidatorFactory.apply(clientRegistration);
|
||||
jwtDecoder.setJwtValidator(jwtValidator);
|
||||
jwtDecoder.setJwtValidator(this.jwtValidatorFactory.apply(clientRegistration));
|
||||
Converter<Map<String, Object>, Map<String, Object>> claimTypeConverter =
|
||||
this.claimTypeConverterFactory.apply(clientRegistration);
|
||||
if (claimTypeConverter != null) {
|
||||
jwtDecoder.setClaimSetConverter(claimTypeConverter);
|
||||
}
|
||||
return jwtDecoder;
|
||||
});
|
||||
}
|
||||
@@ -163,4 +210,16 @@ public final class OidcIdTokenDecoderFactory implements JwtDecoderFactory<Client
|
||||
Assert.notNull(jwsAlgorithmResolver, "jwsAlgorithmResolver cannot be null");
|
||||
this.jwsAlgorithmResolver = jwsAlgorithmResolver;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the factory that provides a {@link Converter} used for type conversion of claim values for an {@link OidcIdToken}.
|
||||
* The default is {@link ClaimTypeConverter} for all {@link ClientRegistration clients}.
|
||||
*
|
||||
* @param claimTypeConverterFactory the factory that provides a {@link Converter} used for type conversion
|
||||
* of claim values for a specific {@link ClientRegistration client}
|
||||
*/
|
||||
public final void setClaimTypeConverterFactory(Function<ClientRegistration, Converter<Map<String, Object>, Map<String, Object>>> claimTypeConverterFactory) {
|
||||
Assert.notNull(claimTypeConverterFactory, "claimTypeConverterFactory cannot be null");
|
||||
this.claimTypeConverterFactory = claimTypeConverterFactory;
|
||||
}
|
||||
}
|
||||
|
||||
+61
-2
@@ -15,11 +15,17 @@
|
||||
*/
|
||||
package org.springframework.security.oauth2.client.oidc.authentication;
|
||||
|
||||
import org.springframework.core.convert.TypeDescriptor;
|
||||
import org.springframework.core.convert.converter.Converter;
|
||||
import org.springframework.security.oauth2.client.registration.ClientRegistration;
|
||||
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
|
||||
import org.springframework.security.oauth2.core.OAuth2Error;
|
||||
import org.springframework.security.oauth2.core.OAuth2TokenValidator;
|
||||
import org.springframework.security.oauth2.core.converter.ClaimConversionService;
|
||||
import org.springframework.security.oauth2.core.converter.ClaimTypeConverter;
|
||||
import org.springframework.security.oauth2.core.oidc.IdTokenClaimNames;
|
||||
import org.springframework.security.oauth2.core.oidc.OidcIdToken;
|
||||
import org.springframework.security.oauth2.core.oidc.StandardClaimNames;
|
||||
import org.springframework.security.oauth2.jose.jws.JwsAlgorithm;
|
||||
import org.springframework.security.oauth2.jose.jws.MacAlgorithm;
|
||||
import org.springframework.security.oauth2.jose.jws.SignatureAlgorithm;
|
||||
@@ -31,7 +37,10 @@ import org.springframework.util.Assert;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
import java.net.URL;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.time.Instant;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
@@ -61,17 +70,55 @@ public final class ReactiveOidcIdTokenDecoderFactory implements ReactiveJwtDecod
|
||||
put(MacAlgorithm.HS512, "HmacSHA512");
|
||||
}
|
||||
};
|
||||
private static final Converter<Map<String, Object>, Map<String, Object>> DEFAULT_CLAIM_TYPE_CONVERTER =
|
||||
new ClaimTypeConverter(createDefaultClaimTypeConverters());
|
||||
private final Map<String, ReactiveJwtDecoder> jwtDecoders = new ConcurrentHashMap<>();
|
||||
private Function<ClientRegistration, OAuth2TokenValidator<Jwt>> jwtValidatorFactory = OidcIdTokenValidator::new;
|
||||
private Function<ClientRegistration, JwsAlgorithm> jwsAlgorithmResolver = clientRegistration -> SignatureAlgorithm.RS256;
|
||||
private Function<ClientRegistration, Converter<Map<String, Object>, Map<String, Object>>> claimTypeConverterFactory =
|
||||
clientRegistration -> DEFAULT_CLAIM_TYPE_CONVERTER;
|
||||
|
||||
/**
|
||||
* Returns the default {@link Converter}'s used for type conversion of claim values for an {@link OidcIdToken}.
|
||||
*
|
||||
* @return a {@link Map} of {@link Converter}'s keyed by {@link IdTokenClaimNames claim name}
|
||||
*/
|
||||
public static Map<String, Converter<Object, ?>> createDefaultClaimTypeConverters() {
|
||||
Converter<Object, ?> booleanConverter = getConverter(TypeDescriptor.valueOf(Boolean.class));
|
||||
Converter<Object, ?> instantConverter = getConverter(TypeDescriptor.valueOf(Instant.class));
|
||||
Converter<Object, ?> urlConverter = getConverter(TypeDescriptor.valueOf(URL.class));
|
||||
Converter<Object, ?> collectionStringConverter = getConverter(
|
||||
TypeDescriptor.collection(Collection.class, TypeDescriptor.valueOf(String.class)));
|
||||
|
||||
Map<String, Converter<Object, ?>> claimTypeConverters = new HashMap<>();
|
||||
claimTypeConverters.put(IdTokenClaimNames.ISS, urlConverter);
|
||||
claimTypeConverters.put(IdTokenClaimNames.AUD, collectionStringConverter);
|
||||
claimTypeConverters.put(IdTokenClaimNames.EXP, instantConverter);
|
||||
claimTypeConverters.put(IdTokenClaimNames.IAT, instantConverter);
|
||||
claimTypeConverters.put(IdTokenClaimNames.AUTH_TIME, instantConverter);
|
||||
claimTypeConverters.put(IdTokenClaimNames.AMR, collectionStringConverter);
|
||||
claimTypeConverters.put(StandardClaimNames.EMAIL_VERIFIED, booleanConverter);
|
||||
claimTypeConverters.put(StandardClaimNames.PHONE_NUMBER_VERIFIED, booleanConverter);
|
||||
claimTypeConverters.put(StandardClaimNames.UPDATED_AT, instantConverter);
|
||||
return claimTypeConverters;
|
||||
}
|
||||
|
||||
private static Converter<Object, ?> getConverter(TypeDescriptor targetDescriptor) {
|
||||
final TypeDescriptor sourceDescriptor = TypeDescriptor.valueOf(Object.class);
|
||||
return source -> ClaimConversionService.getSharedInstance().convert(source, sourceDescriptor, targetDescriptor);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ReactiveJwtDecoder createDecoder(ClientRegistration clientRegistration) {
|
||||
Assert.notNull(clientRegistration, "clientRegistration cannot be null");
|
||||
return this.jwtDecoders.computeIfAbsent(clientRegistration.getRegistrationId(), key -> {
|
||||
NimbusReactiveJwtDecoder jwtDecoder = buildDecoder(clientRegistration);
|
||||
OAuth2TokenValidator<Jwt> jwtValidator = this.jwtValidatorFactory.apply(clientRegistration);
|
||||
jwtDecoder.setJwtValidator(jwtValidator);
|
||||
jwtDecoder.setJwtValidator(this.jwtValidatorFactory.apply(clientRegistration));
|
||||
Converter<Map<String, Object>, Map<String, Object>> claimTypeConverter =
|
||||
this.claimTypeConverterFactory.apply(clientRegistration);
|
||||
if (claimTypeConverter != null) {
|
||||
jwtDecoder.setClaimSetConverter(claimTypeConverter);
|
||||
}
|
||||
return jwtDecoder;
|
||||
});
|
||||
}
|
||||
@@ -163,4 +210,16 @@ public final class ReactiveOidcIdTokenDecoderFactory implements ReactiveJwtDecod
|
||||
Assert.notNull(jwsAlgorithmResolver, "jwsAlgorithmResolver cannot be null");
|
||||
this.jwsAlgorithmResolver = jwsAlgorithmResolver;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the factory that provides a {@link Converter} used for type conversion of claim values for an {@link OidcIdToken}.
|
||||
* The default is {@link ClaimTypeConverter} for all {@link ClientRegistration clients}.
|
||||
*
|
||||
* @param claimTypeConverterFactory the factory that provides a {@link Converter} used for type conversion
|
||||
* of claim values for a specific {@link ClientRegistration client}
|
||||
*/
|
||||
public final void setClaimTypeConverterFactory(Function<ClientRegistration, Converter<Map<String, Object>, Map<String, Object>>> claimTypeConverterFactory) {
|
||||
Assert.notNull(claimTypeConverterFactory, "claimTypeConverterFactory cannot be null");
|
||||
this.claimTypeConverterFactory = claimTypeConverterFactory;
|
||||
}
|
||||
}
|
||||
|
||||
+65
-5
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2018 the original author or authors.
|
||||
* Copyright 2002-2019 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -15,25 +15,34 @@
|
||||
*/
|
||||
package org.springframework.security.oauth2.client.oidc.userinfo;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import org.springframework.core.convert.TypeDescriptor;
|
||||
import org.springframework.core.convert.converter.Converter;
|
||||
import org.springframework.security.core.GrantedAuthority;
|
||||
import org.springframework.security.oauth2.client.registration.ClientRegistration;
|
||||
import org.springframework.security.oauth2.client.userinfo.DefaultReactiveOAuth2UserService;
|
||||
import org.springframework.security.oauth2.client.userinfo.OAuth2UserRequest;
|
||||
import org.springframework.security.oauth2.client.userinfo.ReactiveOAuth2UserService;
|
||||
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.OidcUserInfo;
|
||||
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.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;
|
||||
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.function.Function;
|
||||
|
||||
/**
|
||||
* An implementation of an {@link ReactiveOAuth2UserService} that supports OpenID Connect 1.0 Provider's.
|
||||
*
|
||||
@@ -50,8 +59,36 @@ public class OidcReactiveOAuth2UserService implements
|
||||
|
||||
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 ReactiveOAuth2UserService<OAuth2UserRequest, OAuth2User> oauth2UserService = new DefaultReactiveOAuth2UserService();
|
||||
|
||||
private Function<ClientRegistration, Converter<Map<String, Object>, Map<String, Object>>> claimTypeConverterFactory =
|
||||
clientRegistration -> DEFAULT_CLAIM_TYPE_CONVERTER;
|
||||
|
||||
/**
|
||||
* Returns the default {@link Converter}'s used for type conversion of claim values for an {@link OidcUserInfo}.
|
||||
|
||||
* @since 5.2
|
||||
* @return a {@link Map} of {@link Converter}'s keyed by {@link StandardClaimNames claim name}
|
||||
*/
|
||||
public static Map<String, Converter<Object, ?>> createDefaultClaimTypeConverters() {
|
||||
Converter<Object, ?> booleanConverter = getConverter(TypeDescriptor.valueOf(Boolean.class));
|
||||
Converter<Object, ?> instantConverter = getConverter(TypeDescriptor.valueOf(Instant.class));
|
||||
|
||||
Map<String, Converter<Object, ?>> claimTypeConverters = new HashMap<>();
|
||||
claimTypeConverters.put(StandardClaimNames.EMAIL_VERIFIED, booleanConverter);
|
||||
claimTypeConverters.put(StandardClaimNames.PHONE_NUMBER_VERIFIED, booleanConverter);
|
||||
claimTypeConverters.put(StandardClaimNames.UPDATED_AT, instantConverter);
|
||||
return claimTypeConverters;
|
||||
}
|
||||
|
||||
private static Converter<Object, ?> getConverter(TypeDescriptor targetDescriptor) {
|
||||
final TypeDescriptor sourceDescriptor = TypeDescriptor.valueOf(Object.class);
|
||||
return source -> ClaimConversionService.getSharedInstance().convert(source, sourceDescriptor, targetDescriptor);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<OidcUser> loadUser(OidcUserRequest userRequest) throws OAuth2AuthenticationException {
|
||||
Assert.notNull(userRequest, "userRequest cannot be null");
|
||||
@@ -76,8 +113,10 @@ public class OidcReactiveOAuth2UserService implements
|
||||
if (!OidcUserRequestUtils.shouldRetrieveUserInfo(userRequest)) {
|
||||
return Mono.empty();
|
||||
}
|
||||
|
||||
return this.oauth2UserService.loadUser(userRequest)
|
||||
.map(OAuth2User::getAttributes)
|
||||
.map(claims -> convertClaims(claims, userRequest.getClientRegistration()))
|
||||
.map(OidcUserInfo::new)
|
||||
.doOnNext(userInfo -> {
|
||||
String subject = userInfo.getSubject();
|
||||
@@ -88,8 +127,29 @@ public class OidcReactiveOAuth2UserService implements
|
||||
});
|
||||
}
|
||||
|
||||
private Map<String, Object> convertClaims(Map<String, Object> claims, ClientRegistration clientRegistration) {
|
||||
Converter<Map<String, Object>, Map<String, Object>> claimTypeConverter =
|
||||
this.claimTypeConverterFactory.apply(clientRegistration);
|
||||
return claimTypeConverter != null ?
|
||||
claimTypeConverter.convert(claims) :
|
||||
DEFAULT_CLAIM_TYPE_CONVERTER.convert(claims);
|
||||
}
|
||||
|
||||
public void setOauth2UserService(ReactiveOAuth2UserService<OAuth2UserRequest, OAuth2User> oauth2UserService) {
|
||||
Assert.notNull(oauth2UserService, "oauth2UserService cannot be null");
|
||||
this.oauth2UserService = oauth2UserService;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the factory that provides a {@link Converter} used for type conversion of claim values for an {@link OidcUserInfo}.
|
||||
* The default is {@link ClaimTypeConverter} for all {@link ClientRegistration clients}.
|
||||
*
|
||||
* @since 5.2
|
||||
* @param claimTypeConverterFactory the factory that provides a {@link Converter} used for type conversion
|
||||
* of claim values for a specific {@link ClientRegistration client}
|
||||
*/
|
||||
public final void setClaimTypeConverterFactory(Function<ClientRegistration, Converter<Map<String, Object>, Map<String, Object>>> claimTypeConverterFactory) {
|
||||
Assert.notNull(claimTypeConverterFactory, "claimTypeConverterFactory cannot be null");
|
||||
this.claimTypeConverterFactory = claimTypeConverterFactory;
|
||||
}
|
||||
}
|
||||
|
||||
+60
-2
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2018 the original author or authors.
|
||||
* Copyright 2002-2019 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -15,15 +15,21 @@
|
||||
*/
|
||||
package org.springframework.security.oauth2.client.oidc.userinfo;
|
||||
|
||||
import org.springframework.core.convert.TypeDescriptor;
|
||||
import org.springframework.core.convert.converter.Converter;
|
||||
import org.springframework.security.core.GrantedAuthority;
|
||||
import org.springframework.security.oauth2.client.registration.ClientRegistration;
|
||||
import org.springframework.security.oauth2.client.userinfo.DefaultOAuth2UserService;
|
||||
import org.springframework.security.oauth2.client.userinfo.OAuth2UserRequest;
|
||||
import org.springframework.security.oauth2.client.userinfo.OAuth2UserService;
|
||||
import org.springframework.security.oauth2.core.AuthorizationGrantType;
|
||||
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.OidcScopes;
|
||||
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;
|
||||
import org.springframework.security.oauth2.core.oidc.user.OidcUser;
|
||||
import org.springframework.security.oauth2.core.oidc.user.OidcUserAuthority;
|
||||
@@ -32,10 +38,14 @@ import org.springframework.util.Assert;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.function.Function;
|
||||
|
||||
/**
|
||||
* An implementation of an {@link OAuth2UserService} that supports OpenID Connect 1.0 Provider's.
|
||||
@@ -50,9 +60,35 @@ import java.util.Set;
|
||||
*/
|
||||
public class OidcUserService implements OAuth2UserService<OidcUserRequest, OidcUser> {
|
||||
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 OAuth2UserService<OAuth2UserRequest, OAuth2User> oauth2UserService = new DefaultOAuth2UserService();
|
||||
private Function<ClientRegistration, Converter<Map<String, Object>, Map<String, Object>>> claimTypeConverterFactory =
|
||||
clientRegistration -> DEFAULT_CLAIM_TYPE_CONVERTER;
|
||||
|
||||
/**
|
||||
* Returns the default {@link Converter}'s used for type conversion of claim values for an {@link OidcUserInfo}.
|
||||
|
||||
* @since 5.2
|
||||
* @return a {@link Map} of {@link Converter}'s keyed by {@link StandardClaimNames claim name}
|
||||
*/
|
||||
public static Map<String, Converter<Object, ?>> createDefaultClaimTypeConverters() {
|
||||
Converter<Object, ?> booleanConverter = getConverter(TypeDescriptor.valueOf(Boolean.class));
|
||||
Converter<Object, ?> instantConverter = getConverter(TypeDescriptor.valueOf(Instant.class));
|
||||
|
||||
Map<String, Converter<Object, ?>> claimTypeConverters = new HashMap<>();
|
||||
claimTypeConverters.put(StandardClaimNames.EMAIL_VERIFIED, booleanConverter);
|
||||
claimTypeConverters.put(StandardClaimNames.PHONE_NUMBER_VERIFIED, booleanConverter);
|
||||
claimTypeConverters.put(StandardClaimNames.UPDATED_AT, instantConverter);
|
||||
return claimTypeConverters;
|
||||
}
|
||||
|
||||
private static Converter<Object, ?> getConverter(TypeDescriptor targetDescriptor) {
|
||||
final TypeDescriptor sourceDescriptor = TypeDescriptor.valueOf(Object.class);
|
||||
return source -> ClaimConversionService.getSharedInstance().convert(source, sourceDescriptor, targetDescriptor);
|
||||
}
|
||||
|
||||
@Override
|
||||
public OidcUser loadUser(OidcUserRequest userRequest) throws OAuth2AuthenticationException {
|
||||
@@ -60,7 +96,16 @@ public class OidcUserService implements OAuth2UserService<OidcUserRequest, OidcU
|
||||
OidcUserInfo userInfo = null;
|
||||
if (this.shouldRetrieveUserInfo(userRequest)) {
|
||||
OAuth2User oauth2User = this.oauth2UserService.loadUser(userRequest);
|
||||
userInfo = new OidcUserInfo(oauth2User.getAttributes());
|
||||
|
||||
Map<String, Object> claims;
|
||||
Converter<Map<String, Object>, Map<String, Object>> claimTypeConverter =
|
||||
this.claimTypeConverterFactory.apply(userRequest.getClientRegistration());
|
||||
if (claimTypeConverter != null) {
|
||||
claims = claimTypeConverter.convert(oauth2User.getAttributes());
|
||||
} else {
|
||||
claims = DEFAULT_CLAIM_TYPE_CONVERTER.convert(oauth2User.getAttributes());
|
||||
}
|
||||
userInfo = new OidcUserInfo(claims);
|
||||
|
||||
// https://openid.net/specs/openid-connect-core-1_0.html#UserInfoResponse
|
||||
|
||||
@@ -132,4 +177,17 @@ public class OidcUserService implements OAuth2UserService<OidcUserRequest, OidcU
|
||||
Assert.notNull(oauth2UserService, "oauth2UserService cannot be null");
|
||||
this.oauth2UserService = oauth2UserService;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the factory that provides a {@link Converter} used for type conversion of claim values for an {@link OidcUserInfo}.
|
||||
* The default is {@link ClaimTypeConverter} for all {@link ClientRegistration clients}.
|
||||
*
|
||||
* @since 5.2
|
||||
* @param claimTypeConverterFactory the factory that provides a {@link Converter} used for type conversion
|
||||
* of claim values for a specific {@link ClientRegistration client}
|
||||
*/
|
||||
public final void setClaimTypeConverterFactory(Function<ClientRegistration, Converter<Map<String, Object>, Map<String, Object>>> claimTypeConverterFactory) {
|
||||
Assert.notNull(claimTypeConverterFactory, "claimTypeConverterFactory cannot be null");
|
||||
this.claimTypeConverterFactory = claimTypeConverterFactory;
|
||||
}
|
||||
}
|
||||
|
||||
+40
@@ -17,15 +17,20 @@ package org.springframework.security.oauth2.client.oidc.authentication;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.springframework.core.convert.converter.Converter;
|
||||
import org.springframework.security.oauth2.client.registration.ClientRegistration;
|
||||
import org.springframework.security.oauth2.client.registration.TestClientRegistrations;
|
||||
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
|
||||
import org.springframework.security.oauth2.core.OAuth2TokenValidator;
|
||||
import org.springframework.security.oauth2.core.converter.ClaimTypeConverter;
|
||||
import org.springframework.security.oauth2.core.oidc.IdTokenClaimNames;
|
||||
import org.springframework.security.oauth2.core.oidc.StandardClaimNames;
|
||||
import org.springframework.security.oauth2.jose.jws.JwsAlgorithm;
|
||||
import org.springframework.security.oauth2.jose.jws.MacAlgorithm;
|
||||
import org.springframework.security.oauth2.jose.jws.SignatureAlgorithm;
|
||||
import org.springframework.security.oauth2.jwt.Jwt;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.function.Function;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
@@ -49,6 +54,20 @@ public class OidcIdTokenDecoderFactoryTests {
|
||||
this.idTokenDecoderFactory = new OidcIdTokenDecoderFactory();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void createDefaultClaimTypeConvertersWhenCalledThenDefaultsAreCorrect() {
|
||||
Map<String, Converter<Object, ?>> claimTypeConverters = OidcIdTokenDecoderFactory.createDefaultClaimTypeConverters();
|
||||
assertThat(claimTypeConverters).containsKey(IdTokenClaimNames.ISS);
|
||||
assertThat(claimTypeConverters).containsKey(IdTokenClaimNames.AUD);
|
||||
assertThat(claimTypeConverters).containsKey(IdTokenClaimNames.EXP);
|
||||
assertThat(claimTypeConverters).containsKey(IdTokenClaimNames.IAT);
|
||||
assertThat(claimTypeConverters).containsKey(IdTokenClaimNames.AUTH_TIME);
|
||||
assertThat(claimTypeConverters).containsKey(IdTokenClaimNames.AMR);
|
||||
assertThat(claimTypeConverters).containsKey(StandardClaimNames.EMAIL_VERIFIED);
|
||||
assertThat(claimTypeConverters).containsKey(StandardClaimNames.PHONE_NUMBER_VERIFIED);
|
||||
assertThat(claimTypeConverters).containsKey(StandardClaimNames.UPDATED_AT);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setJwtValidatorFactoryWhenNullThenThrowIllegalArgumentException() {
|
||||
assertThatThrownBy(() -> this.idTokenDecoderFactory.setJwtValidatorFactory(null))
|
||||
@@ -61,6 +80,12 @@ public class OidcIdTokenDecoderFactoryTests {
|
||||
.isInstanceOf(IllegalArgumentException.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setClaimTypeConverterFactoryWhenNullThenThrowIllegalArgumentException() {
|
||||
assertThatThrownBy(() -> this.idTokenDecoderFactory.setClaimTypeConverterFactory(null))
|
||||
.isInstanceOf(IllegalArgumentException.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void createDecoderWhenClientRegistrationNullThenThrowIllegalArgumentException() {
|
||||
assertThatThrownBy(() -> this.idTokenDecoderFactory.createDecoder(null))
|
||||
@@ -141,4 +166,19 @@ public class OidcIdTokenDecoderFactoryTests {
|
||||
|
||||
verify(customJwsAlgorithmResolver).apply(same(clientRegistration));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void createDecoderWhenCustomClaimTypeConverterFactorySetThenApplied() {
|
||||
Function<ClientRegistration, Converter<Map<String, Object>, Map<String, Object>>> customClaimTypeConverterFactory = mock(Function.class);
|
||||
this.idTokenDecoderFactory.setClaimTypeConverterFactory(customClaimTypeConverterFactory);
|
||||
|
||||
ClientRegistration clientRegistration = this.registration.build();
|
||||
|
||||
when(customClaimTypeConverterFactory.apply(same(clientRegistration)))
|
||||
.thenReturn(new ClaimTypeConverter(OidcIdTokenDecoderFactory.createDefaultClaimTypeConverters()));
|
||||
|
||||
this.idTokenDecoderFactory.createDecoder(clientRegistration);
|
||||
|
||||
verify(customClaimTypeConverterFactory).apply(same(clientRegistration));
|
||||
}
|
||||
}
|
||||
|
||||
+40
@@ -17,15 +17,20 @@ package org.springframework.security.oauth2.client.oidc.authentication;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.springframework.core.convert.converter.Converter;
|
||||
import org.springframework.security.oauth2.client.registration.ClientRegistration;
|
||||
import org.springframework.security.oauth2.client.registration.TestClientRegistrations;
|
||||
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
|
||||
import org.springframework.security.oauth2.core.OAuth2TokenValidator;
|
||||
import org.springframework.security.oauth2.core.converter.ClaimTypeConverter;
|
||||
import org.springframework.security.oauth2.core.oidc.IdTokenClaimNames;
|
||||
import org.springframework.security.oauth2.core.oidc.StandardClaimNames;
|
||||
import org.springframework.security.oauth2.jose.jws.JwsAlgorithm;
|
||||
import org.springframework.security.oauth2.jose.jws.MacAlgorithm;
|
||||
import org.springframework.security.oauth2.jose.jws.SignatureAlgorithm;
|
||||
import org.springframework.security.oauth2.jwt.Jwt;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.function.Function;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
@@ -49,6 +54,20 @@ public class ReactiveOidcIdTokenDecoderFactoryTests {
|
||||
this.idTokenDecoderFactory = new ReactiveOidcIdTokenDecoderFactory();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void createDefaultClaimTypeConvertersWhenCalledThenDefaultsAreCorrect() {
|
||||
Map<String, Converter<Object, ?>> claimTypeConverters = ReactiveOidcIdTokenDecoderFactory.createDefaultClaimTypeConverters();
|
||||
assertThat(claimTypeConverters).containsKey(IdTokenClaimNames.ISS);
|
||||
assertThat(claimTypeConverters).containsKey(IdTokenClaimNames.AUD);
|
||||
assertThat(claimTypeConverters).containsKey(IdTokenClaimNames.EXP);
|
||||
assertThat(claimTypeConverters).containsKey(IdTokenClaimNames.IAT);
|
||||
assertThat(claimTypeConverters).containsKey(IdTokenClaimNames.AUTH_TIME);
|
||||
assertThat(claimTypeConverters).containsKey(IdTokenClaimNames.AMR);
|
||||
assertThat(claimTypeConverters).containsKey(StandardClaimNames.EMAIL_VERIFIED);
|
||||
assertThat(claimTypeConverters).containsKey(StandardClaimNames.PHONE_NUMBER_VERIFIED);
|
||||
assertThat(claimTypeConverters).containsKey(StandardClaimNames.UPDATED_AT);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setJwtValidatorFactoryWhenNullThenThrowIllegalArgumentException() {
|
||||
assertThatThrownBy(() -> this.idTokenDecoderFactory.setJwtValidatorFactory(null))
|
||||
@@ -61,6 +80,12 @@ public class ReactiveOidcIdTokenDecoderFactoryTests {
|
||||
.isInstanceOf(IllegalArgumentException.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setClaimTypeConverterFactoryWhenNullThenThrowIllegalArgumentException() {
|
||||
assertThatThrownBy(() -> this.idTokenDecoderFactory.setClaimTypeConverterFactory(null))
|
||||
.isInstanceOf(IllegalArgumentException.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void createDecoderWhenClientRegistrationNullThenThrowIllegalArgumentException() {
|
||||
assertThatThrownBy(() -> this.idTokenDecoderFactory.createDecoder(null))
|
||||
@@ -141,4 +166,19 @@ public class ReactiveOidcIdTokenDecoderFactoryTests {
|
||||
|
||||
verify(customJwsAlgorithmResolver).apply(same(clientRegistration));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void createDecoderWhenCustomClaimTypeConverterFactorySetThenApplied() {
|
||||
Function<ClientRegistration, Converter<Map<String, Object>, Map<String, Object>>> customClaimTypeConverterFactory = mock(Function.class);
|
||||
this.idTokenDecoderFactory.setClaimTypeConverterFactory(customClaimTypeConverterFactory);
|
||||
|
||||
ClientRegistration clientRegistration = this.registration.build();
|
||||
|
||||
when(customClaimTypeConverterFactory.apply(same(clientRegistration)))
|
||||
.thenReturn(new ClaimTypeConverter(OidcIdTokenDecoderFactory.createDefaultClaimTypeConverters()));
|
||||
|
||||
this.idTokenDecoderFactory.createDecoder(clientRegistration);
|
||||
|
||||
verify(customClaimTypeConverterFactory).apply(same(clientRegistration));
|
||||
}
|
||||
}
|
||||
|
||||
+42
-4
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2018 the original author or authors.
|
||||
* Copyright 2002-2019 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -21,6 +21,7 @@ import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.MockitoJUnitRunner;
|
||||
import org.springframework.core.convert.converter.Converter;
|
||||
import org.springframework.security.core.authority.AuthorityUtils;
|
||||
import org.springframework.security.oauth2.client.registration.ClientRegistration;
|
||||
import org.springframework.security.oauth2.client.registration.TestClientRegistrations;
|
||||
@@ -28,6 +29,7 @@ import org.springframework.security.oauth2.client.userinfo.OAuth2UserRequest;
|
||||
import org.springframework.security.oauth2.client.userinfo.ReactiveOAuth2UserService;
|
||||
import org.springframework.security.oauth2.core.OAuth2AccessToken;
|
||||
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
|
||||
import org.springframework.security.oauth2.core.converter.ClaimTypeConverter;
|
||||
import org.springframework.security.oauth2.core.oidc.IdTokenClaimNames;
|
||||
import org.springframework.security.oauth2.core.oidc.OidcIdToken;
|
||||
import org.springframework.security.oauth2.core.oidc.StandardClaimNames;
|
||||
@@ -41,11 +43,11 @@ import java.time.Instant;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.function.Function;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatCode;
|
||||
import static org.assertj.core.api.Assertions.*;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.when;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
/**
|
||||
* @author Rob Winch
|
||||
@@ -76,6 +78,20 @@ public class OidcReactiveOAuth2UserServiceTests {
|
||||
this.userService.setOauth2UserService(this.oauth2UserService);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void createDefaultClaimTypeConvertersWhenCalledThenDefaultsAreCorrect() {
|
||||
Map<String, Converter<Object, ?>> claimTypeConverters = OidcReactiveOAuth2UserService.createDefaultClaimTypeConverters();
|
||||
assertThat(claimTypeConverters).containsKey(StandardClaimNames.EMAIL_VERIFIED);
|
||||
assertThat(claimTypeConverters).containsKey(StandardClaimNames.PHONE_NUMBER_VERIFIED);
|
||||
assertThat(claimTypeConverters).containsKey(StandardClaimNames.UPDATED_AT);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setClaimTypeConverterFactoryWhenNullThenThrowIllegalArgumentException() {
|
||||
assertThatThrownBy(() -> this.userService.setClaimTypeConverterFactory(null))
|
||||
.isInstanceOf(IllegalArgumentException.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void loadUserWhenUserInfoUriNullThenUserInfoNotRetrieved() {
|
||||
this.registration.userInfoUri(null);
|
||||
@@ -141,6 +157,28 @@ public class OidcReactiveOAuth2UserServiceTests {
|
||||
assertThat(this.userService.loadUser(userRequest()).block().getName()).isEqualTo("rob");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void loadUserWhenCustomClaimTypeConverterFactorySetThenApplied() {
|
||||
Map<String, Object> attributes = new HashMap<>();
|
||||
attributes.put(StandardClaimNames.SUB, "sub123");
|
||||
attributes.put("user", "rob");
|
||||
OAuth2User oauth2User = new DefaultOAuth2User(AuthorityUtils.createAuthorityList("ROLE_USER"),
|
||||
attributes, "user");
|
||||
when(this.oauth2UserService.loadUser(any())).thenReturn(Mono.just(oauth2User));
|
||||
|
||||
OidcUserRequest userRequest = userRequest();
|
||||
|
||||
Function<ClientRegistration, Converter<Map<String, Object>, Map<String, Object>>> customClaimTypeConverterFactory = mock(Function.class);
|
||||
this.userService.setClaimTypeConverterFactory(customClaimTypeConverterFactory);
|
||||
|
||||
when(customClaimTypeConverterFactory.apply(same(userRequest.getClientRegistration())))
|
||||
.thenReturn(new ClaimTypeConverter(OidcReactiveOAuth2UserService.createDefaultClaimTypeConverters()));
|
||||
|
||||
this.userService.loadUser(userRequest).block().getUserInfo();
|
||||
|
||||
verify(customClaimTypeConverterFactory).apply(same(userRequest.getClientRegistration()));
|
||||
}
|
||||
|
||||
private OidcUserRequest userRequest() {
|
||||
return new OidcUserRequest(this.registration.build(), this.accessToken, this.idToken);
|
||||
}
|
||||
|
||||
+57
-10
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2018 the original author or authors.
|
||||
* Copyright 2002-2019 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -15,14 +15,6 @@
|
||||
*/
|
||||
package org.springframework.security.oauth2.client.oidc.userinfo;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import okhttp3.mockwebserver.MockResponse;
|
||||
import okhttp3.mockwebserver.MockWebServer;
|
||||
import okhttp3.mockwebserver.RecordedRequest;
|
||||
@@ -31,7 +23,7 @@ import org.junit.Before;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.ExpectedException;
|
||||
|
||||
import org.springframework.core.convert.converter.Converter;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.http.MediaType;
|
||||
@@ -40,6 +32,7 @@ import org.springframework.security.oauth2.client.userinfo.DefaultOAuth2UserServ
|
||||
import org.springframework.security.oauth2.core.AuthenticationMethod;
|
||||
import org.springframework.security.oauth2.core.OAuth2AccessToken;
|
||||
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
|
||||
import org.springframework.security.oauth2.core.converter.ClaimTypeConverter;
|
||||
import org.springframework.security.oauth2.core.oidc.IdTokenClaimNames;
|
||||
import org.springframework.security.oauth2.core.oidc.OidcIdToken;
|
||||
import org.springframework.security.oauth2.core.oidc.OidcScopes;
|
||||
@@ -47,9 +40,20 @@ import org.springframework.security.oauth2.core.oidc.StandardClaimNames;
|
||||
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.HashMap;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.function.Function;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
||||
import static org.hamcrest.CoreMatchers.containsString;
|
||||
import static org.mockito.ArgumentMatchers.same;
|
||||
import static org.mockito.Mockito.*;
|
||||
import static org.springframework.security.oauth2.client.registration.TestClientRegistrations.clientRegistration;
|
||||
import static org.springframework.security.oauth2.core.TestOAuth2AccessTokens.scopes;
|
||||
|
||||
@@ -92,12 +96,26 @@ public class OidcUserServiceTests {
|
||||
this.server.shutdown();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void createDefaultClaimTypeConvertersWhenCalledThenDefaultsAreCorrect() {
|
||||
Map<String, Converter<Object, ?>> claimTypeConverters = OidcUserService.createDefaultClaimTypeConverters();
|
||||
assertThat(claimTypeConverters).containsKey(StandardClaimNames.EMAIL_VERIFIED);
|
||||
assertThat(claimTypeConverters).containsKey(StandardClaimNames.PHONE_NUMBER_VERIFIED);
|
||||
assertThat(claimTypeConverters).containsKey(StandardClaimNames.UPDATED_AT);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setOauth2UserServiceWhenNullThenThrowIllegalArgumentException() {
|
||||
assertThatThrownBy(() -> this.userService.setOauth2UserService(null))
|
||||
.isInstanceOf(IllegalArgumentException.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setClaimTypeConverterFactoryWhenNullThenThrowIllegalArgumentException() {
|
||||
assertThatThrownBy(() -> this.userService.setClaimTypeConverterFactory(null))
|
||||
.isInstanceOf(IllegalArgumentException.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void loadUserWhenUserRequestIsNullThenThrowIllegalArgumentException() {
|
||||
this.exception.expect(IllegalArgumentException.class);
|
||||
@@ -355,6 +373,35 @@ public class OidcUserServiceTests {
|
||||
assertThat(request.getBody().readUtf8()).isEqualTo("access_token=" + this.accessToken.getTokenValue());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void loadUserWhenCustomClaimTypeConverterFactorySetThenApplied() {
|
||||
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();
|
||||
|
||||
Function<ClientRegistration, Converter<Map<String, Object>, Map<String, Object>>> customClaimTypeConverterFactory = mock(Function.class);
|
||||
this.userService.setClaimTypeConverterFactory(customClaimTypeConverterFactory);
|
||||
|
||||
when(customClaimTypeConverterFactory.apply(same(clientRegistration)))
|
||||
.thenReturn(new ClaimTypeConverter(OidcUserService.createDefaultClaimTypeConverters()));
|
||||
|
||||
this.userService.loadUser(new OidcUserRequest(clientRegistration, this.accessToken, this.idToken));
|
||||
|
||||
verify(customClaimTypeConverterFactory).apply(same(clientRegistration));
|
||||
}
|
||||
|
||||
private MockResponse jsonResponse(String json) {
|
||||
return new MockResponse()
|
||||
.setHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
|
||||
|
||||
Reference in New Issue
Block a user