From 0c55bfce677366a8d0a17e690c6822abb90ff878 Mon Sep 17 00:00:00 2001 From: Brian Demers Date: Mon, 12 Jul 2021 15:45:30 -0400 Subject: [PATCH] Updates Jackson usage to use immutable ObjectReader/Writer instead of ObjectMapper Jackson 2.10+ recommend using `ObjectReader` and `ObjectWriter` as opposed to `ObjectMapper` https://cowtowncoder.medium.com/jackson-3-0-immutability-w-builders-d9c532860d88 --- CHANGELOG.md | 3 +- .../jackson/io/JacksonDeserializer.java | 30 +++++++++++-------- .../jackson/io/JacksonSerializer.java | 7 +++-- .../jackson/io/JacksonDeserializerTest.groovy | 4 +-- .../jackson/io/JacksonSerializerTest.groovy | 4 +-- pom.xml | 2 +- 6 files changed, 29 insertions(+), 21 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 90e26e43..4cda0fb5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,7 +5,8 @@ This patch release: * Adds additional handling for rare JSON parsing exceptions and wraps them in a `JwtException` to allow the application to handle these conditions as JWT concerns. -* Upgrades the `jjwt-jackson` module's Jackson dependency to `2.9.10.7`. +* Upgrades the `jjwt-jackson` module's Jackson dependency to `2.12.4`. +* Updates Jackson usage (in `jjwt-jackson`) to use immutable classes instead of using `ObjectMapper` directly. ### 0.11.2 diff --git a/extensions/jackson/src/main/java/io/jsonwebtoken/jackson/io/JacksonDeserializer.java b/extensions/jackson/src/main/java/io/jsonwebtoken/jackson/io/JacksonDeserializer.java index a9ea111e..3367a946 100644 --- a/extensions/jackson/src/main/java/io/jsonwebtoken/jackson/io/JacksonDeserializer.java +++ b/extensions/jackson/src/main/java/io/jsonwebtoken/jackson/io/JacksonDeserializer.java @@ -19,6 +19,7 @@ import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.databind.DeserializationContext; import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.ObjectReader; import com.fasterxml.jackson.databind.deser.std.UntypedObjectDeserializer; import com.fasterxml.jackson.databind.module.SimpleModule; import io.jsonwebtoken.io.DeserializationException; @@ -35,7 +36,7 @@ import java.util.Map; public class JacksonDeserializer implements Deserializer { private final Class returnType; - private final ObjectMapper objectMapper; + private final ObjectReader objectReader; @SuppressWarnings("unused") //used via reflection by RuntimeClasspathDeserializerLocator public JacksonDeserializer() { @@ -68,14 +69,7 @@ public class JacksonDeserializer implements Deserializer { * @param claimTypeMap The claim name-to-class map used to deserialize claims into the given type */ public JacksonDeserializer(Map claimTypeMap) { - // DO NOT reuse JacksonSerializer.DEFAULT_OBJECT_MAPPER as this could result in sharing the custom deserializer - // between instances - this(new ObjectMapper()); - Assert.notNull(claimTypeMap, "Claim type map cannot be null."); - // register a new Deserializer - SimpleModule module = new SimpleModule(); - module.addDeserializer(Object.class, new MappedTypeDeserializer(Collections.unmodifiableMap(claimTypeMap))); - objectMapper.registerModule(module); + this(objectMapperWithMappedTypes(claimTypeMap)); } @SuppressWarnings({"unchecked", "WeakerAccess", "unused"}) // for end-users providing a custom ObjectMapper @@ -86,7 +80,7 @@ public class JacksonDeserializer implements Deserializer { private JacksonDeserializer(ObjectMapper objectMapper, Class returnType) { Assert.notNull(objectMapper, "ObjectMapper cannot be null."); Assert.notNull(returnType, "Return type cannot be null."); - this.objectMapper = objectMapper; + this.objectReader = objectMapper.reader(); this.returnType = returnType; } @@ -101,7 +95,19 @@ public class JacksonDeserializer implements Deserializer { } protected T readValue(byte[] bytes) throws IOException { - return objectMapper.readValue(bytes, returnType); + return objectReader.readValue(bytes, returnType); + } + + private static ObjectMapper objectMapperWithMappedTypes(Map claimTypeMap) { + // DO NOT reuse JacksonSerializer.DEFAULT_OBJECT_MAPPER as this could result in sharing the custom deserializer + // between instances + Assert.notNull(claimTypeMap, "Claim type map cannot be null."); + ObjectMapper objectMapper = new ObjectMapper(); + // register a new Deserializer + SimpleModule module = new SimpleModule(); + module.addDeserializer(Object.class, new MappedTypeDeserializer(Collections.unmodifiableMap(claimTypeMap))); + objectMapper.registerModule(module); + return objectMapper; } /** @@ -122,7 +128,7 @@ public class JacksonDeserializer implements Deserializer { // check if the current claim key is mapped, if so traverse it's value String name = parser.currentName(); if (claimTypeMap != null && name != null && claimTypeMap.containsKey(name)) { - Class type = claimTypeMap.get(name); + Class type = claimTypeMap.get(name); return parser.readValueAsTree().traverse(parser.getCodec()).readValueAs(type); } // otherwise default to super diff --git a/extensions/jackson/src/main/java/io/jsonwebtoken/jackson/io/JacksonSerializer.java b/extensions/jackson/src/main/java/io/jsonwebtoken/jackson/io/JacksonSerializer.java index 1445f92d..1cb71247 100644 --- a/extensions/jackson/src/main/java/io/jsonwebtoken/jackson/io/JacksonSerializer.java +++ b/extensions/jackson/src/main/java/io/jsonwebtoken/jackson/io/JacksonSerializer.java @@ -17,6 +17,7 @@ package io.jsonwebtoken.jackson.io; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.ObjectWriter; import io.jsonwebtoken.io.SerializationException; import io.jsonwebtoken.io.Serializer; import io.jsonwebtoken.lang.Assert; @@ -28,7 +29,7 @@ public class JacksonSerializer implements Serializer { static final ObjectMapper DEFAULT_OBJECT_MAPPER = new ObjectMapper(); - private final ObjectMapper objectMapper; + private final ObjectWriter objectWriter; @SuppressWarnings("unused") //used via reflection by RuntimeClasspathDeserializerLocator public JacksonSerializer() { @@ -38,7 +39,7 @@ public class JacksonSerializer implements Serializer { @SuppressWarnings("WeakerAccess") //intended for end-users to use when providing a custom ObjectMapper public JacksonSerializer(ObjectMapper objectMapper) { Assert.notNull(objectMapper, "ObjectMapper cannot be null."); - this.objectMapper = objectMapper; + this.objectWriter = objectMapper.writer(); } @Override @@ -54,6 +55,6 @@ public class JacksonSerializer implements Serializer { @SuppressWarnings("WeakerAccess") //for testing protected byte[] writeValueAsBytes(T t) throws JsonProcessingException { - return this.objectMapper.writeValueAsBytes(t); + return this.objectWriter.writeValueAsBytes(t); } } diff --git a/extensions/jackson/src/test/groovy/io/jsonwebtoken/jackson/io/JacksonDeserializerTest.groovy b/extensions/jackson/src/test/groovy/io/jsonwebtoken/jackson/io/JacksonDeserializerTest.groovy index a2b7e8fb..ccccf6f6 100644 --- a/extensions/jackson/src/test/groovy/io/jsonwebtoken/jackson/io/JacksonDeserializerTest.groovy +++ b/extensions/jackson/src/test/groovy/io/jsonwebtoken/jackson/io/JacksonDeserializerTest.groovy @@ -38,14 +38,14 @@ class JacksonDeserializerTest { @Test void testDefaultConstructor() { def deserializer = new JacksonDeserializer() - assertNotNull deserializer.objectMapper + assertNotNull deserializer.objectReader } @Test void testObjectMapperConstructor() { def customOM = new ObjectMapper() def deserializer = new JacksonDeserializer(customOM) - assertSame customOM, deserializer.objectMapper + assertSame customOM.getDeserializationConfig(), deserializer.objectReader.config } @Test(expected = IllegalArgumentException) diff --git a/extensions/jackson/src/test/groovy/io/jsonwebtoken/jackson/io/JacksonSerializerTest.groovy b/extensions/jackson/src/test/groovy/io/jsonwebtoken/jackson/io/JacksonSerializerTest.groovy index 1febe76e..309b5d51 100644 --- a/extensions/jackson/src/test/groovy/io/jsonwebtoken/jackson/io/JacksonSerializerTest.groovy +++ b/extensions/jackson/src/test/groovy/io/jsonwebtoken/jackson/io/JacksonSerializerTest.groovy @@ -37,14 +37,14 @@ class JacksonSerializerTest { @Test void testDefaultConstructor() { def serializer = new JacksonSerializer() - assertNotNull serializer.objectMapper + assertNotNull serializer.objectWriter } @Test void testObjectMapperConstructor() { def customOM = new ObjectMapper() def serializer = new JacksonSerializer<>(customOM) - assertSame customOM, serializer.objectMapper + assertSame customOM.getSerializationConfig(), serializer.objectWriter.config } @Test(expected = IllegalArgumentException) diff --git a/pom.xml b/pom.xml index cbb9955c..79586b1c 100644 --- a/pom.xml +++ b/pom.xml @@ -74,7 +74,7 @@ UTF-8 ${user.name}-${maven.build.timestamp} - 2.9.10.7 + 2.12.4 20180130 2.8.5