Skip to content

Commit e85a00a

Browse files
snagojimmyjames
andauthored
Preserve insertion order for claims (#656)
Co-authored-by: Jim Anderson <jim.anderson@auth0.com>
1 parent a18955b commit e85a00a

File tree

2 files changed

+52
-5
lines changed

2 files changed

+52
-5
lines changed

lib/src/main/java/com/auth0/jwt/JWTCreator.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -71,8 +71,8 @@ public static class Builder {
7171
private final Map<String, Object> headerClaims;
7272

7373
Builder() {
74-
this.payloadClaims = new HashMap<>();
75-
this.headerClaims = new HashMap<>();
74+
this.payloadClaims = new LinkedHashMap<>();
75+
this.headerClaims = new LinkedHashMap<>();
7676
}
7777

7878
/**
@@ -112,7 +112,7 @@ public Builder withHeader(String headerClaimsJson) throws IllegalArgumentExcepti
112112
}
113113

114114
try {
115-
Map<String, Object> headerClaims = mapper.readValue(headerClaimsJson, HashMap.class);
115+
Map<String, Object> headerClaims = mapper.readValue(headerClaimsJson, LinkedHashMap.class);
116116
return withHeader(headerClaims);
117117
} catch (JsonProcessingException e) {
118118
throw new IllegalArgumentException("Invalid header JSON", e);
@@ -508,7 +508,7 @@ public Builder withPayload(String payloadClaimsJson) throws IllegalArgumentExcep
508508
}
509509

510510
try {
511-
Map<String, Object> payloadClaims = mapper.readValue(payloadClaimsJson, HashMap.class);
511+
Map<String, Object> payloadClaims = mapper.readValue(payloadClaimsJson, LinkedHashMap.class);
512512
return withPayload(payloadClaims);
513513
} catch (JsonProcessingException e) {
514514
throw new IllegalArgumentException("Invalid payload JSON", e);

lib/src/test/java/com/auth0/jwt/JWTCreatorTest.java

Lines changed: 48 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@
33
import com.auth0.jwt.algorithms.Algorithm;
44
import com.auth0.jwt.interfaces.ECDSAKeyProvider;
55
import com.auth0.jwt.interfaces.RSAKeyProvider;
6-
import com.fasterxml.jackson.core.JsonProcessingException;
76
import com.fasterxml.jackson.databind.ObjectMapper;
7+
import com.fasterxml.jackson.databind.node.ObjectNode;
88
import org.junit.Rule;
99
import org.junit.Test;
1010
import org.junit.rules.ExpectedException;
@@ -1010,4 +1010,51 @@ public void shouldCreatePayloadWithNullForList() {
10101010
assertThat(jwt, is(notNullValue()));
10111011
assertTrue(JWT.decode(jwt).getClaim("name").isNull());
10121012
}
1013+
1014+
@Test
1015+
public void shouldPreserveInsertionOrder() throws Exception {
1016+
String taxonomyJson = "{\"class\": \"mammalia\", \"order\": \"carnivora\", \"family\": \"canidae\", \"genus\": \"vulpes\"}";
1017+
List<String> taxonomyClaims = Arrays.asList("class", "order", "family", "genus");
1018+
List<String> headerInsertionOrder = new ArrayList<>(taxonomyClaims);
1019+
Map<String, Object> header = new LinkedHashMap<>();
1020+
for (int i = 0; i < 10; i++) {
1021+
String key = "h" + i;
1022+
header.put(key, "v" + 1);
1023+
headerInsertionOrder.add(key);
1024+
}
1025+
1026+
List<String> payloadInsertionOrder = new ArrayList<>(taxonomyClaims);
1027+
JWTCreator.Builder builder = JWTCreator.init()
1028+
.withHeader(taxonomyJson)
1029+
.withHeader(header)
1030+
.withPayload(taxonomyJson);
1031+
for (int i = 0; i < 10; i++) {
1032+
String name = "c" + i;
1033+
builder = builder.withClaim(name, "v" + i);
1034+
payloadInsertionOrder.add(name);
1035+
}
1036+
String signed = builder.sign(Algorithm.HMAC256("secret"));
1037+
1038+
assertThat(signed, is(notNullValue()));
1039+
String[] parts = signed.split("\\.");
1040+
Base64.Decoder urlDecoder = Base64.getUrlDecoder();
1041+
String headerJson = new String(urlDecoder.decode(parts[0]), StandardCharsets.UTF_8);
1042+
String payloadJson = new String(urlDecoder.decode(parts[1]), StandardCharsets.UTF_8);
1043+
1044+
ObjectMapper objectMapper = new ObjectMapper();
1045+
1046+
List<String> headerFields = new ArrayList<>();
1047+
objectMapper.readValue(headerJson, ObjectNode.class)
1048+
.fieldNames().forEachRemaining(headerFields::add);
1049+
headerFields.retainAll(headerInsertionOrder);
1050+
assertThat("Header insertion order should be preserved",
1051+
headerFields, is(equalTo(headerInsertionOrder)));
1052+
1053+
List<String> payloadFields = new ArrayList<>();
1054+
objectMapper.readValue(payloadJson, ObjectNode.class)
1055+
.fieldNames().forEachRemaining(payloadFields::add);
1056+
payloadFields.retainAll(payloadInsertionOrder);
1057+
assertThat("Claim insertion order should be preserved",
1058+
payloadFields, is(equalTo(payloadInsertionOrder)));
1059+
}
10131060
}

0 commit comments

Comments
 (0)