Skip to content

Commit 9925c6a

Browse files
Fabio Guencijzheaux
authored andcommitted
Preserve Null Claim Values
Prior to this commit ClaimTypeConverter returned the claims with the original value for all the claims with a null converted value. The changes allows ClaimTypeConverter to overwrite and return claims with converted value of null. Closes gh-10135
1 parent 0de2a51 commit 9925c6a

File tree

3 files changed

+28
-31
lines changed

3 files changed

+28
-31
lines changed

oauth2/oauth2-core/src/main/java/org/springframework/security/oauth2/core/converter/ClaimTypeConverter.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2019 the original author or authors.
2+
* Copyright 2002-2021 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.

oauth2/oauth2-jose/src/main/java/org/springframework/security/oauth2/jwt/MappedJwtClaimSetConverter.java

Lines changed: 17 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2019 the original author or authors.
2+
* Copyright 2002-2021 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -45,19 +45,20 @@ public final class MappedJwtClaimSetConverter implements Converter<Map<String, O
4545
private final static TypeDescriptor INSTANT_TYPE_DESCRIPTOR = TypeDescriptor.valueOf(Instant.class);
4646
private final static TypeDescriptor URL_TYPE_DESCRIPTOR = TypeDescriptor.valueOf(URL.class);
4747
private final Map<String, Converter<Object, ?>> claimTypeConverters;
48-
private final Converter<Map<String, Object>, Map<String, Object>> delegate;
4948

5049
/**
5150
* Constructs a {@link MappedJwtClaimSetConverter} with the provided arguments
5251
*
5352
* This will completely replace any set of default converters.
5453
*
54+
* A converter that returns {@code null} removes the claim from the claim set. A
55+
* converter that returns a non-{@code null} value adds or replaces that claim in the
56+
* claim set.
5557
* @param claimTypeConverters The {@link Map} of converters to use
5658
*/
5759
public MappedJwtClaimSetConverter(Map<String, Converter<Object, ?>> claimTypeConverters) {
5860
Assert.notNull(claimTypeConverters, "claimTypeConverters cannot be null");
5961
this.claimTypeConverters = claimTypeConverters;
60-
this.delegate = new ClaimTypeConverter(claimTypeConverters);
6162
}
6263

6364
/**
@@ -81,6 +82,9 @@ public MappedJwtClaimSetConverter(Map<String, Converter<Object, ?>> claimTypeCon
8182
*
8283
* To completely replace the underlying {@link Map} of converters, see {@link MappedJwtClaimSetConverter#MappedJwtClaimSetConverter(Map)}.
8384
*
85+
* A converter that returns {@code null} removes the claim from the claim set. A
86+
* converter that returns a non-{@code null} value adds or replaces that claim in the
87+
* claim set.
8488
* @param claimTypeConverters
8589
* @return An instance of {@link MappedJwtClaimSetConverter} that contains the converters provided,
8690
* plus any defaults that were not overridden.
@@ -144,12 +148,16 @@ private static String convertIssuer(Object source) {
144148
@Override
145149
public Map<String, Object> convert(Map<String, Object> claims) {
146150
Assert.notNull(claims, "claims cannot be null");
147-
148-
Map<String, Object> mappedClaims = this.delegate.convert(claims);
149-
150-
mappedClaims = removeClaims(mappedClaims);
151-
mappedClaims = addClaims(mappedClaims);
152-
151+
Map<String, Object> mappedClaims = new HashMap<>(claims);
152+
for (Map.Entry<String, Converter<Object, ?>> entry : this.claimTypeConverters.entrySet()) {
153+
String claimName = entry.getKey();
154+
Converter<Object, ?> converter = entry.getValue();
155+
if (converter != null) {
156+
Object claim = claims.get(claimName);
157+
Object mappedClaim = converter.convert(claim);
158+
mappedClaims.compute(claimName, (key, value) -> mappedClaim);
159+
}
160+
}
153161
Instant issuedAt = (Instant) mappedClaims.get(JwtClaimNames.IAT);
154162
Instant expiresAt = (Instant) mappedClaims.get(JwtClaimNames.EXP);
155163
if (issuedAt == null && expiresAt != null) {
@@ -159,23 +167,4 @@ public Map<String, Object> convert(Map<String, Object> claims) {
159167
return mappedClaims;
160168
}
161169

162-
private Map<String, Object> removeClaims(Map<String, Object> claims) {
163-
Map<String, Object> result = new HashMap<>();
164-
for (Map.Entry<String, Object> entry : claims.entrySet()) {
165-
if (entry.getValue() != null) {
166-
result.put(entry.getKey(), entry.getValue());
167-
}
168-
}
169-
return result;
170-
}
171-
172-
private Map<String, Object> addClaims(Map<String, Object> claims) {
173-
Map<String, Object> result = new HashMap<>(claims);
174-
for (Map.Entry<String, Converter<Object, ?>> entry : claimTypeConverters.entrySet()) {
175-
if (!claims.containsKey(entry.getKey()) && entry.getValue().convert(null) != null) {
176-
result.put(entry.getKey(), entry.getValue().convert(null));
177-
}
178-
}
179-
return result;
180-
}
181170
}

oauth2/oauth2-jose/src/test/java/org/springframework/security/oauth2/jwt/MappedJwtClaimSetConverterTests.java

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2018 the original author or authors.
2+
* Copyright 2002-2021 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -140,11 +140,19 @@ public void convertWhenUsingCustomConverterThenAllOtherDefaultsAreStillUsed() {
140140
assertThat(target.get(JwtClaimNames.SUB)).isEqualTo("1234");
141141
}
142142

143+
// gh-10135
143144
@Test
144145
public void convertWhenConverterReturnsNullThenClaimIsRemoved() {
145146
MappedJwtClaimSetConverter converter = MappedJwtClaimSetConverter
146-
.withDefaults(Collections.emptyMap());
147+
.withDefaults(Collections.singletonMap(JwtClaimNames.NBF, (nbfClaimValue) -> null));
148+
Map<String, Object> source = Collections.singletonMap(JwtClaimNames.NBF, Instant.now());
149+
Map<String, Object> target = converter.convert(source);
150+
assertThat(target).doesNotContainKey(JwtClaimNames.NBF);
151+
}
147152

153+
@Test
154+
public void convertWhenClaimValueIsNullThenClaimIsRemoved() {
155+
MappedJwtClaimSetConverter converter = MappedJwtClaimSetConverter.withDefaults(Collections.emptyMap());
148156
Map<String, Object> source = Collections.singletonMap(JwtClaimNames.ISS, null);
149157
Map<String, Object> target = converter.convert(source);
150158

0 commit comments

Comments
 (0)