|
15 | 15 | */
|
16 | 16 | package org.springframework.security.oauth2.client.oidc.authentication;
|
17 | 17 |
|
| 18 | +import org.springframework.core.convert.TypeDescriptor; |
| 19 | +import org.springframework.core.convert.converter.Converter; |
18 | 20 | import org.springframework.security.oauth2.client.registration.ClientRegistration;
|
19 | 21 | import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
|
20 | 22 | import org.springframework.security.oauth2.core.OAuth2Error;
|
21 | 23 | import org.springframework.security.oauth2.core.OAuth2TokenValidator;
|
| 24 | +import org.springframework.security.oauth2.core.converter.ClaimConversionService; |
| 25 | +import org.springframework.security.oauth2.core.converter.ClaimTypeConverter; |
| 26 | +import org.springframework.security.oauth2.core.oidc.IdTokenClaimNames; |
22 | 27 | import org.springframework.security.oauth2.core.oidc.OidcIdToken;
|
| 28 | +import org.springframework.security.oauth2.core.oidc.StandardClaimNames; |
23 | 29 | import org.springframework.security.oauth2.jose.jws.JwsAlgorithm;
|
24 | 30 | import org.springframework.security.oauth2.jose.jws.MacAlgorithm;
|
25 | 31 | import org.springframework.security.oauth2.jose.jws.SignatureAlgorithm;
|
|
31 | 37 | import org.springframework.util.StringUtils;
|
32 | 38 |
|
33 | 39 | import javax.crypto.spec.SecretKeySpec;
|
| 40 | +import java.net.URL; |
34 | 41 | import java.nio.charset.StandardCharsets;
|
| 42 | +import java.time.Instant; |
| 43 | +import java.util.Collection; |
35 | 44 | import java.util.HashMap;
|
36 | 45 | import java.util.Map;
|
37 | 46 | import java.util.concurrent.ConcurrentHashMap;
|
@@ -61,17 +70,55 @@ public final class ReactiveOidcIdTokenDecoderFactory implements ReactiveJwtDecod
|
61 | 70 | put(MacAlgorithm.HS512, "HmacSHA512");
|
62 | 71 | }
|
63 | 72 | };
|
| 73 | + private static final Converter<Map<String, Object>, Map<String, Object>> DEFAULT_CLAIM_TYPE_CONVERTER = |
| 74 | + new ClaimTypeConverter(createDefaultClaimTypeConverters()); |
64 | 75 | private final Map<String, ReactiveJwtDecoder> jwtDecoders = new ConcurrentHashMap<>();
|
65 | 76 | private Function<ClientRegistration, OAuth2TokenValidator<Jwt>> jwtValidatorFactory = OidcIdTokenValidator::new;
|
66 | 77 | private Function<ClientRegistration, JwsAlgorithm> jwsAlgorithmResolver = clientRegistration -> SignatureAlgorithm.RS256;
|
| 78 | + private Function<ClientRegistration, Converter<Map<String, Object>, Map<String, Object>>> claimTypeConverterFactory = |
| 79 | + clientRegistration -> DEFAULT_CLAIM_TYPE_CONVERTER; |
| 80 | + |
| 81 | + /** |
| 82 | + * Returns the default {@link Converter}'s used for type conversion of claim values for an {@link OidcIdToken}. |
| 83 | + * |
| 84 | + * @return a {@link Map} of {@link Converter}'s keyed by {@link IdTokenClaimNames claim name} |
| 85 | + */ |
| 86 | + public static Map<String, Converter<Object, ?>> createDefaultClaimTypeConverters() { |
| 87 | + Converter<Object, ?> booleanConverter = getConverter(TypeDescriptor.valueOf(Boolean.class)); |
| 88 | + Converter<Object, ?> instantConverter = getConverter(TypeDescriptor.valueOf(Instant.class)); |
| 89 | + Converter<Object, ?> urlConverter = getConverter(TypeDescriptor.valueOf(URL.class)); |
| 90 | + Converter<Object, ?> collectionStringConverter = getConverter( |
| 91 | + TypeDescriptor.collection(Collection.class, TypeDescriptor.valueOf(String.class))); |
| 92 | + |
| 93 | + Map<String, Converter<Object, ?>> claimTypeConverters = new HashMap<>(); |
| 94 | + claimTypeConverters.put(IdTokenClaimNames.ISS, urlConverter); |
| 95 | + claimTypeConverters.put(IdTokenClaimNames.AUD, collectionStringConverter); |
| 96 | + claimTypeConverters.put(IdTokenClaimNames.EXP, instantConverter); |
| 97 | + claimTypeConverters.put(IdTokenClaimNames.IAT, instantConverter); |
| 98 | + claimTypeConverters.put(IdTokenClaimNames.AUTH_TIME, instantConverter); |
| 99 | + claimTypeConverters.put(IdTokenClaimNames.AMR, collectionStringConverter); |
| 100 | + claimTypeConverters.put(StandardClaimNames.EMAIL_VERIFIED, booleanConverter); |
| 101 | + claimTypeConverters.put(StandardClaimNames.PHONE_NUMBER_VERIFIED, booleanConverter); |
| 102 | + claimTypeConverters.put(StandardClaimNames.UPDATED_AT, instantConverter); |
| 103 | + return claimTypeConverters; |
| 104 | + } |
| 105 | + |
| 106 | + private static Converter<Object, ?> getConverter(TypeDescriptor targetDescriptor) { |
| 107 | + final TypeDescriptor sourceDescriptor = TypeDescriptor.valueOf(Object.class); |
| 108 | + return source -> ClaimConversionService.getSharedInstance().convert(source, sourceDescriptor, targetDescriptor); |
| 109 | + } |
67 | 110 |
|
68 | 111 | @Override
|
69 | 112 | public ReactiveJwtDecoder createDecoder(ClientRegistration clientRegistration) {
|
70 | 113 | Assert.notNull(clientRegistration, "clientRegistration cannot be null");
|
71 | 114 | return this.jwtDecoders.computeIfAbsent(clientRegistration.getRegistrationId(), key -> {
|
72 | 115 | NimbusReactiveJwtDecoder jwtDecoder = buildDecoder(clientRegistration);
|
73 |
| - OAuth2TokenValidator<Jwt> jwtValidator = this.jwtValidatorFactory.apply(clientRegistration); |
74 |
| - jwtDecoder.setJwtValidator(jwtValidator); |
| 116 | + jwtDecoder.setJwtValidator(this.jwtValidatorFactory.apply(clientRegistration)); |
| 117 | + Converter<Map<String, Object>, Map<String, Object>> claimTypeConverter = |
| 118 | + this.claimTypeConverterFactory.apply(clientRegistration); |
| 119 | + if (claimTypeConverter != null) { |
| 120 | + jwtDecoder.setClaimSetConverter(claimTypeConverter); |
| 121 | + } |
75 | 122 | return jwtDecoder;
|
76 | 123 | });
|
77 | 124 | }
|
@@ -163,4 +210,16 @@ public final void setJwsAlgorithmResolver(Function<ClientRegistration, JwsAlgori
|
163 | 210 | Assert.notNull(jwsAlgorithmResolver, "jwsAlgorithmResolver cannot be null");
|
164 | 211 | this.jwsAlgorithmResolver = jwsAlgorithmResolver;
|
165 | 212 | }
|
| 213 | + |
| 214 | + /** |
| 215 | + * Sets the factory that provides a {@link Converter} used for type conversion of claim values for an {@link OidcIdToken}. |
| 216 | + * The default is {@link ClaimTypeConverter} for all {@link ClientRegistration clients}. |
| 217 | + * |
| 218 | + * @param claimTypeConverterFactory the factory that provides a {@link Converter} used for type conversion |
| 219 | + * of claim values for a specific {@link ClientRegistration client} |
| 220 | + */ |
| 221 | + public final void setClaimTypeConverterFactory(Function<ClientRegistration, Converter<Map<String, Object>, Map<String, Object>>> claimTypeConverterFactory) { |
| 222 | + Assert.notNull(claimTypeConverterFactory, "claimTypeConverterFactory cannot be null"); |
| 223 | + this.claimTypeConverterFactory = claimTypeConverterFactory; |
| 224 | + } |
166 | 225 | }
|
0 commit comments