Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This file should be removed (it would be in the root of the project if it needs to be added)

Empty file.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This file should be removed (it would be in the root of the project if it needs to be added)

Empty file.
63 changes: 63 additions & 0 deletions extensions/jackson3/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<parent>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-root</artifactId>
<version>0.14.0-SNAPSHOT</version>
<relativePath>../../pom.xml</relativePath>
</parent>

<artifactId>jjwt-jackson3</artifactId>
<name>JJWT :: Extensions :: Jackson3</name>
<packaging>jar</packaging>

<properties>
<jjwt.root>${basedir}/../..</jjwt.root>
</properties>


<dependencies>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-api</artifactId>
</dependency>
<dependency>
<groupId>tools.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
</dependencies>

<build>
<plugins>
<!-- The following plugin section is used in jjwt-jackson and jjwt-orgjson, to repackage (and verify)
binary compatibility with previous versions. In v0.11.0 the implementations changed packages to
avoid split package issues with Java 9+ see: https://github.com/jwtk/jjwt/issues/399 -->
<!-- TODO: remove these deprecated packages and this config before v1.0 -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<configuration>
<relocations>
<relocation>
<pattern>io.jsonwebtoken.jackson.io</pattern>
<shadedPattern>io.jsonwebtoken.io</shadedPattern>
<includes>io.jsonwebtoken.jackson.io.*</includes>
</relocation>
</relocations>
</configuration>
</plugin>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks like it was copypasta from the jackson2 module. It shouldn't be needed here as there is no backward compatibility concerns.

<!--plugin>
<groupId>com.github.siom79.japicmp</groupId>
<artifactId>japicmp-maven-plugin</artifactId>
<configuration>
<parameter>
<ignoreMissingClasses>true</ignoreMissingClasses>
</parameter>
</configuration>
</plugin-->
</plugins>
</build>
</project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
/*
* Copyright (C) 2014 jsonwebtoken.io
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
* Copyright (C) 2014 jsonwebtoken.io
* Copyright (C) 2025 jsonwebtoken.io

*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.jsonwebtoken.jackson.io;

import io.jsonwebtoken.io.AbstractDeserializer;
import io.jsonwebtoken.lang.Assert;
import tools.jackson.core.JacksonException;
import tools.jackson.core.JsonParser;
import tools.jackson.databind.DeserializationContext;
import tools.jackson.databind.JavaType;
import tools.jackson.databind.JsonNode;
import tools.jackson.databind.ObjectMapper;
import tools.jackson.databind.deser.jdk.UntypedObjectDeserializer;
import tools.jackson.databind.module.SimpleModule;

import java.io.Reader;
import java.util.Collections;
import java.util.Map;

/**
* Deserializer using a Jackson {@link ObjectMapper}.
*
* @since 0.10.0
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: use 0.14 (or {NEXT_VERSION})

*/
public class Jackson3Deserializer<T> extends AbstractDeserializer<T> {

private final Class<T> returnType;

private final ObjectMapper objectMapper;

/**
* Constructor using JJWT's default {@link ObjectMapper} singleton for deserialization.
*/
public Jackson3Deserializer() {
this(Jackson3Serializer.DEFAULT_OBJECT_MAPPER);
}

/**
* Creates a new JacksonDeserializer where the values of the claims can be parsed into given types. A common usage
* example is to parse custom User object out of a claim, for example the claims:
* <pre>{@code
* {
* "issuer": "https://issuer.example.com",
* "user": {
* "firstName": "Jill",
* "lastName": "Coder"
* }
* }}</pre>
* Passing a map of {@code ["user": User.class]} to this constructor would result in the {@code user} claim being
* transformed to an instance of your custom {@code User} class, instead of the default of {@code Map}.
* <p>
* Because custom type parsing requires modifying the state of a Jackson {@code ObjectMapper}, this
* constructor creates a new internal {@code ObjectMapper} instance and customizes it to support the
* specified {@code claimTypeMap}. This ensures that the JJWT parsing behavior does not unexpectedly
* modify the state of another application-specific {@code ObjectMapper}.
* <p>
* If you would like to use your own {@code ObjectMapper} instance that also supports custom types for
* JWT {@code Claims}, you will need to first customize your {@code ObjectMapper} instance by registering
* your custom types and then use the {@link #Jackson3Deserializer(ObjectMapper)} constructor instead.
*
* @param claimTypeMap The claim name-to-class map used to deserialize claims into the given type
*/
public Jackson3Deserializer(Map<String, Class<?>> claimTypeMap) {
// DO NOT specify JacksonSerializer.DEFAULT_OBJECT_MAPPER here as that would modify the shared instance
this(Jackson3Serializer.newObjectMapper(), claimTypeMap);
}

/**
* Constructor using the specified Jackson {@link ObjectMapper}.
*
* @param objectMapper the ObjectMapper to use for deserialization.
*/
@SuppressWarnings("unchecked")
public Jackson3Deserializer(ObjectMapper objectMapper) {
this(objectMapper, (Class<T>) Object.class);
}

/**
* Creates a new JacksonDeserializer where the values of the claims can be parsed into given types by registering
* a type-converting {@link tools.jackson.databind.JacksonModule Module} on the specified {@link ObjectMapper}.
* A common usage example is to parse custom User object out of a claim, for example the claims:
* <pre>{@code
* {
* "issuer": "https://issuer.example.com",
* "user": {
* "firstName": "Jill",
* "lastName": "Coder"
* }
* }}</pre>
* Passing a map of {@code ["user": User.class]} to this constructor would result in the {@code user} claim being
* transformed to an instance of your custom {@code User} class, instead of the default of {@code Map}.
* <p>
* Because custom type parsing requires modifying the state of a Jackson {@code ObjectMapper}, this
* constructor modifies the specified {@code objectMapper} argument and customizes it to support the
* specified {@code claimTypeMap}.
* <p>
* If you do not want your {@code ObjectMapper} instance modified, but also want to support custom types for
* JWT {@code Claims}, you will need to first customize your {@code ObjectMapper} instance by registering
* your custom types separately and then use the {@link #Jackson3Deserializer(ObjectMapper)} constructor instead
* (which does not modify the {@code objectMapper} argument).
*
* @param objectMapper the objectMapper to modify by registering a custom type-converting
* {@link tools.jackson.databind.JacksonModule Module}
* @param claimTypeMap The claim name-to-class map used to deserialize claims into the given type
* @since 0.13.0
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
* @since 0.13.0

*/
public Jackson3Deserializer(ObjectMapper objectMapper, Map<String, Class<?>> claimTypeMap) {
this(objectMapper, (Class<T>) Object.class, claimTypeMap);
}

private Jackson3Deserializer(ObjectMapper objectMapper, Class<T> returnType) {
Assert.notNull(objectMapper, "ObjectMapper cannot be null.");
Assert.notNull(returnType, "Return type cannot be null.");
this.objectMapper = objectMapper;
this.returnType = returnType;
}

private Jackson3Deserializer(ObjectMapper objectMapper, Class<T> returnType, Map<String, Class<?>> claimTypeMap) {
Assert.notNull(objectMapper, "ObjectMapper cannot be null.");
Assert.notNull(returnType, "Return type cannot be null.");
Assert.notNull(claimTypeMap, "Claim type map cannot be null.");
// register a new Deserializer on the ObjectMapper instance:
SimpleModule module = new SimpleModule();
module.addDeserializer(Object.class, new MappedTypeDeserializer(Collections.unmodifiableMap(claimTypeMap)));
this.objectMapper = objectMapper.rebuild().addModule(module).build();
this.returnType = returnType;
}

@Override
protected T doDeserialize(Reader reader) throws Exception {
return objectMapper.readValue(reader, returnType);
}

/**
* A Jackson {@link tools.jackson.databind.deser.std.StdDeserializer JsonDeserializer}, that will convert claim
* values to types based on {@code claimTypeMap}.
*/
private static class MappedTypeDeserializer extends UntypedObjectDeserializer {

private final Map<String, Class<?>> claimTypeMap;

private MappedTypeDeserializer(Map<String, Class<?>> claimTypeMap) {
super((JavaType) null, null);
this.claimTypeMap = claimTypeMap;
}

@Override
public Object deserialize(JsonParser parser, DeserializationContext context) throws JacksonException {
// 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);
//noinspection resource
JsonNode node = parser.readValueAsTree();
return context.readTreeAsValue(node, type);
}
// otherwise default to super
return super.deserialize(parser, context);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
/*
* Copyright (C) 2014 jsonwebtoken.io
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
* Copyright (C) 2014 jsonwebtoken.io
* Copyright (C) 2025 jsonwebtoken.io

*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.jsonwebtoken.jackson.io;

import io.jsonwebtoken.io.AbstractSerializer;
import io.jsonwebtoken.lang.Assert;
import tools.jackson.core.StreamReadFeature;
import tools.jackson.databind.DeserializationFeature;
import tools.jackson.databind.JacksonModule;
import tools.jackson.databind.ObjectMapper;
import tools.jackson.databind.ObjectWriter;
import tools.jackson.databind.module.SimpleModule;

import java.io.OutputStream;

import static tools.jackson.core.StreamWriteFeature.AUTO_CLOSE_TARGET;
import static tools.jackson.databind.json.JsonMapper.builder;

/**
* Serializer using a Jackson {@link ObjectMapper}.
*
* @since 0.10.0
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See version comment above

*/
public class Jackson3Serializer<T> extends AbstractSerializer<T> {

static final String MODULE_ID = "jjwt-jackson";
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This probably doesn't matter at runtime but should this be jjwt-jackson3 to reduce confusion

static final JacksonModule MODULE;

static {
SimpleModule module = new SimpleModule(MODULE_ID);
module.addSerializer(Jackson3SupplierSerializer.INSTANCE);
MODULE = module;
}

static final ObjectMapper DEFAULT_OBJECT_MAPPER = newObjectMapper();

/**
* Creates and returns a new ObjectMapper with the {@code jjwt-jackson} module registered and
* {@code JsonParser.Feature.STRICT_DUPLICATE_DETECTION} enabled (set to true) and
* {@code DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES} disabled (set to false).
*
* @return a new ObjectMapper with the {@code jjwt-jackson} module registered and
* {@code JsonParser.Feature.STRICT_DUPLICATE_DETECTION} enabled (set to true) and
* {@code DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES} disabled (set to false).
*
* @since 0.12.4
*/
Comment on lines +58 to +60
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
*
* @since 0.12.4
*/
*/

// package protected on purpose, do not expose to the public API
static ObjectMapper newObjectMapper() {
return builder().addModule(MODULE)
.configure(StreamReadFeature.STRICT_DUPLICATE_DETECTION, true) // https://github.com/jwtk/jjwt/issues/877
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false) // https://github.com/jwtk/jjwt/issues/893
.build();
}

protected final ObjectMapper objectMapper;

/**
* Constructor using JJWT's default {@link ObjectMapper} singleton for serialization.
*/
public Jackson3Serializer() {
this(DEFAULT_OBJECT_MAPPER);
}

/**
* Creates a new Jackson Serializer that uses the specified {@link ObjectMapper} for serialization.
*
* @param objectMapper the ObjectMapper to use for serialization.
*/
public Jackson3Serializer(ObjectMapper objectMapper) {
Assert.notNull(objectMapper, "ObjectMapper cannot be null.");
this.objectMapper = objectMapper.rebuild().addModule(MODULE).build();
}

@Override
protected void doSerialize(T t, OutputStream out) throws Exception {
Assert.notNull(out, "OutputStream cannot be null.");

ObjectWriter writer = this.objectMapper.writer().without(AUTO_CLOSE_TARGET);
writer.writeValue(out, t);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
/*
* Copyright (C) 2022 jsonwebtoken.io
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
* Copyright (C) 2022 jsonwebtoken.io
* Copyright (C) 2025 jsonwebtoken.io

*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.jsonwebtoken.jackson.io;


import io.jsonwebtoken.lang.Supplier;
import tools.jackson.core.JacksonException;
import tools.jackson.core.JsonGenerator;
import tools.jackson.databind.SerializationContext;
import tools.jackson.databind.ValueSerializer;
import tools.jackson.databind.ser.std.StdSerializer;

final class Jackson3SupplierSerializer extends StdSerializer<Supplier<?>> {

static final Jackson3SupplierSerializer INSTANCE = new Jackson3SupplierSerializer();

public Jackson3SupplierSerializer() {
super(Supplier.class, false);
}

/**
* @param supplier
* @param generator
* @param provider
* @throws JacksonException
*/
Comment on lines +34 to +39
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: fill in, or remove javadoc

@Override
public void serialize(Supplier<?> supplier, JsonGenerator generator, SerializationContext provider) throws JacksonException {
Object value = supplier.get();

if (value == null) {
provider.defaultSerializeNullValue(generator);
return;
}

Class<?> clazz = value.getClass();
ValueSerializer<Object> ser = provider.findTypedValueSerializer(clazz, true);
ser.serialize(value, generator, provider);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
io.jsonwebtoken.jackson.io.Jackson3Deserializer
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
io.jsonwebtoken.jackson.io.Jackson3Serializer
Loading
Loading