-
Notifications
You must be signed in to change notification settings - Fork 1.4k
Added parser for Jackson3 #1032
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,46 @@ | ||
| <?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> | ||
| <!--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,180 @@ | ||
| /* | ||
| * 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 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.14.0 | ||
| */ | ||
| 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); | ||
| } | ||
|
|
||
| /** | ||
| * Deserializer using a Jackson {@link ObjectMapper}. | ||
| * | ||
| * @since 0.14.0 | ||
| * @param objectMapper | ||
| */ | ||
| @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 | ||
| */ | ||
| 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; | ||
| } | ||
|
|
||
| /** | ||
| * | ||
| * @param objectMapper | ||
| * @param returnType | ||
| * @param claimTypeMap | ||
| */ | ||
| 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 | ||||||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||
| * | ||||||||||
| * 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.14.0 | ||||||||||
| */ | ||||||||||
| public class Jackson3Serializer<T> extends AbstractSerializer<T> { | ||||||||||
|
|
||||||||||
| static final String MODULE_ID = "jjwt-jackson3"; | ||||||||||
| 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-jackson3} 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-jackson3} 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
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||
| // 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 | ||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
| * | ||||||
| * 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
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.