Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
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
1 change: 1 addition & 0 deletions extensions/jackson3/bnd.bnd
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Fragment-Host: io.jsonwebtoken.jjwt-api
73 changes: 73 additions & 0 deletions extensions/jackson3/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ Copyright 2018 JWTK
~
~ 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.
-->
<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 https://maven.apache.org/maven-v4_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>
<version>3.0.3</version>
</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>
<plugin>
<groupId>com.github.siom79.japicmp</groupId>
<artifactId>japicmp-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
/*
* Copyright (C) 2014 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 tools.jackson.core.JsonParser;
import tools.jackson.databind.DeserializationContext;
import tools.jackson.databind.ObjectMapper;
import tools.jackson.databind.deser.jdk.UntypedObjectDeserializer;
import tools.jackson.databind.module.SimpleModule;
import io.jsonwebtoken.io.AbstractDeserializer;
import io.jsonwebtoken.lang.Assert;

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

/**
* Deserializer using a Jackson {@link ObjectMapper}.
*
* @since 0.10.0
*/
public class JacksonDeserializer<T> extends AbstractDeserializer<T> {

private final Class<T> returnType;

private final ObjectMapper objectMapper;

/**
* Constructor using JJWT's default {@link ObjectMapper} singleton for deserialization.
*/
public JacksonDeserializer() {
this(JacksonSerializer.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 #JacksonDeserializer(ObjectMapper)} constructor instead.
*
* @param claimTypeMap The claim name-to-class map used to deserialize claims into the given type
*/
public JacksonDeserializer(Map<String, Class<?>> claimTypeMap) {
// DO NOT specify JacksonSerializer.DEFAULT_OBJECT_MAPPER here as that would modify the shared instance
this(JacksonSerializer.newObjectMapper(), claimTypeMap);
}

/**
* Constructor using the specified Jackson {@link ObjectMapper}.
*
* @param objectMapper the ObjectMapper to use for deserialization.
*/
@SuppressWarnings("unchecked")
public JacksonDeserializer(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.Module 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 #JacksonDeserializer(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.Module Module}
* @param claimTypeMap The claim name-to-class map used to deserialize claims into the given type
* @since 0.13.0
*/
public JacksonDeserializer(ObjectMapper objectMapper, Map<String, Class<?>> claimTypeMap) {
Assert.notNull(objectMapper, "ObjectMapper cannot be null.");
Assert.notNull(claimTypeMap, "Claim type map cannot be null.");
// register a new Deserializer on the ObjectMapper instance:
SimpleModule module = new SimpleModule();
// Cast to raw ValueDeserializer to bypass generic checks if needed, or ValueDeserializer<Object>
@SuppressWarnings("unchecked")
tools.jackson.databind.ValueDeserializer<Object> deser = (tools.jackson.databind.ValueDeserializer<Object>) (Object) new MappedTypeDeserializer(Collections.unmodifiableMap(claimTypeMap));
module.addDeserializer(Object.class, deser);
this.objectMapper = objectMapper.rebuild().addModule(module).build();
this.returnType = (Class<T>) Object.class;
}

private JacksonDeserializer(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;
}

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

/**
* A Jackson {@link tools.jackson.databind.JsonDeserializer 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((tools.jackson.databind.JavaType) null, (tools.jackson.databind.JavaType) null);
this.claimTypeMap = claimTypeMap;
}

@Override
public Object deserialize(JsonParser parser, DeserializationContext context) {
// 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);
// Use parser direct read or context
try {
return parser.readValueAs(type);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
// otherwise default to super
return super.deserialize(parser, context);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
/*
* Copyright (C) 2014 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 tools.jackson.core.JsonGenerator;
import tools.jackson.core.JsonParser;
import tools.jackson.core.StreamReadFeature;
import tools.jackson.core.StreamWriteFeature;
import tools.jackson.databind.DeserializationFeature;
import tools.jackson.databind.JacksonModule;
import tools.jackson.databind.ObjectMapper;
import tools.jackson.databind.ObjectWriter;
import tools.jackson.databind.json.JsonMapper;
import tools.jackson.databind.module.SimpleModule;
import io.jsonwebtoken.io.AbstractSerializer;
import io.jsonwebtoken.lang.Assert;

import java.io.OutputStream;

/**
* Serializer using a Jackson {@link ObjectMapper}.
*
* @since 0.10.0
*/
public class JacksonSerializer<T> extends AbstractSerializer<T> {

static final String MODULE_ID = "jjwt-jackson";
static final JacksonModule MODULE;

static {
SimpleModule module = new SimpleModule(MODULE_ID);
module.addSerializer(JacksonSupplierSerializer.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
*/
// package protected on purpose, do not expose to the public API
static ObjectMapper newObjectMapper() {
return JsonMapper.builder()
.addModule(MODULE)
.enable(StreamReadFeature.STRICT_DUPLICATE_DETECTION) // 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 JacksonSerializer() {
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 JacksonSerializer(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(StreamWriteFeature.AUTO_CLOSE_TARGET);
writer.writeValue(out, t);
}
}
Loading