mirror of https://github.com/jwtk/jjwt.git
Merge pull request #344 from jwtk/issue-335-custom-json
Pluggable JSON serialization
This commit is contained in:
commit
29172608a9
27
pom.xml
27
pom.xml
|
@ -86,6 +86,7 @@
|
||||||
<buildNumber>${user.name}-${maven.build.timestamp}</buildNumber>
|
<buildNumber>${user.name}-${maven.build.timestamp}</buildNumber>
|
||||||
|
|
||||||
<jackson.version>2.9.6</jackson.version>
|
<jackson.version>2.9.6</jackson.version>
|
||||||
|
<orgjson.version>20180130</orgjson.version>
|
||||||
|
|
||||||
<!-- Optional Runtime Dependencies: -->
|
<!-- Optional Runtime Dependencies: -->
|
||||||
<bouncycastle.version>1.56</bouncycastle.version>
|
<bouncycastle.version>1.56</bouncycastle.version>
|
||||||
|
@ -98,18 +99,27 @@
|
||||||
<powermock.version>2.0.0-beta.5</powermock.version> <!-- necessary for Java 9 support -->
|
<powermock.version>2.0.0-beta.5</powermock.version> <!-- necessary for Java 9 support -->
|
||||||
<failsafe.plugin.version>2.22.0</failsafe.plugin.version>
|
<failsafe.plugin.version>2.22.0</failsafe.plugin.version>
|
||||||
<surefire.plugin.version>2.22.0</surefire.plugin.version>
|
<surefire.plugin.version>2.22.0</surefire.plugin.version>
|
||||||
<clover.version>4.2.0</clover.version>
|
<clover.version>4.2.1</clover.version>
|
||||||
|
|
||||||
</properties>
|
</properties>
|
||||||
|
|
||||||
<dependencies>
|
<dependencies>
|
||||||
|
|
||||||
|
<!-- Optional Dependencies: -->
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.fasterxml.jackson.core</groupId>
|
<groupId>com.fasterxml.jackson.core</groupId>
|
||||||
<artifactId>jackson-databind</artifactId>
|
<artifactId>jackson-databind</artifactId>
|
||||||
<version>${jackson.version}</version>
|
<version>${jackson.version}</version>
|
||||||
|
<scope>compile</scope>
|
||||||
|
<!-- TODO: make optional after project is broken up into targeted artifacts -->
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.json</groupId>
|
||||||
|
<artifactId>json</artifactId>
|
||||||
|
<version>${orgjson.version}</version>
|
||||||
|
<scope>compile</scope>
|
||||||
|
<optional>true</optional>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<!-- Optional Dependencies: -->
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.bouncycastle</groupId>
|
<groupId>org.bouncycastle</groupId>
|
||||||
<artifactId>bcprov-jdk15on</artifactId>
|
<artifactId>bcprov-jdk15on</artifactId>
|
||||||
|
@ -295,14 +305,13 @@
|
||||||
<version>${clover.version}</version>
|
<version>${clover.version}</version>
|
||||||
<configuration>
|
<configuration>
|
||||||
<excludes>
|
<excludes>
|
||||||
<exclude>**/*Test*</exclude>
|
|
||||||
<!-- leaving out lang as it mostly comes from other sources -->
|
<!-- leaving out lang as it mostly comes from other sources -->
|
||||||
<exclude>io/jsonwebtoken/lang/*</exclude>
|
<exclude>io/jsonwebtoken/lang/*</exclude>
|
||||||
</excludes>
|
</excludes>
|
||||||
<methodPercentage>100%</methodPercentage>
|
<methodPercentage>100.000000%</methodPercentage>
|
||||||
<statementPercentage>100%</statementPercentage>
|
<statementPercentage>100.000000%</statementPercentage>
|
||||||
<conditionalPercentage>100%</conditionalPercentage>
|
<conditionalPercentage>100.000000%</conditionalPercentage>
|
||||||
<targetPercentage>100%</targetPercentage>
|
<targetPercentage>100.000000%</targetPercentage>
|
||||||
</configuration>
|
</configuration>
|
||||||
<executions>
|
<executions>
|
||||||
<execution>
|
<execution>
|
||||||
|
@ -310,8 +319,8 @@
|
||||||
<phase>test</phase>
|
<phase>test</phase>
|
||||||
<goals>
|
<goals>
|
||||||
<goal>instrument</goal>
|
<goal>instrument</goal>
|
||||||
<goal>check</goal>
|
|
||||||
<goal>clover</goal>
|
<goal>clover</goal>
|
||||||
|
<goal>check</goal>
|
||||||
</goals>
|
</goals>
|
||||||
</execution>
|
</execution>
|
||||||
</executions>
|
</executions>
|
||||||
|
|
|
@ -16,6 +16,7 @@
|
||||||
package io.jsonwebtoken;
|
package io.jsonwebtoken;
|
||||||
|
|
||||||
import io.jsonwebtoken.codec.Encoder;
|
import io.jsonwebtoken.codec.Encoder;
|
||||||
|
import io.jsonwebtoken.io.Serializer;
|
||||||
|
|
||||||
import java.security.Key;
|
import java.security.Key;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
|
@ -426,6 +427,20 @@ public interface JwtBuilder extends ClaimsMutator<JwtBuilder> {
|
||||||
*/
|
*/
|
||||||
JwtBuilder base64UrlEncodeWith(Encoder<byte[], String> base64UrlEncoder);
|
JwtBuilder base64UrlEncodeWith(Encoder<byte[], String> base64UrlEncoder);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Performs object-to-JSON serialization with the specified Serializer. This is used by the builder to convert
|
||||||
|
* JWT/JWS/JWT headers and claims Maps to JSON strings as required by the JWT specification.
|
||||||
|
*
|
||||||
|
* <p>If this method is not called, JJWT will use whatever serializer it can find at runtime, checking for the
|
||||||
|
* presence of well-known implementations such Jackson, Gson, and org.json. If one of these is not found
|
||||||
|
* in the runtime classpath, an exception will be thrown when the {@link #compact()} method is invoked.</p>
|
||||||
|
*
|
||||||
|
* @param serializer the serializer to use when converting Map objects to JSON strings.
|
||||||
|
* @return the builder for method chaining.
|
||||||
|
* @since 0.10.0
|
||||||
|
*/
|
||||||
|
JwtBuilder serializeToJsonWith(Serializer<Map<String,?>> serializer);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Actually builds the JWT and serializes it to a compact, URL-safe string according to the
|
* Actually builds the JWT and serializes it to a compact, URL-safe string according to the
|
||||||
* <a href="https://tools.ietf.org/html/draft-ietf-oauth-json-web-token-25#section-7">JWT Compact Serialization</a>
|
* <a href="https://tools.ietf.org/html/draft-ietf-oauth-json-web-token-25#section-7">JWT Compact Serialization</a>
|
||||||
|
|
|
@ -17,9 +17,11 @@ package io.jsonwebtoken;
|
||||||
|
|
||||||
import io.jsonwebtoken.codec.Decoder;
|
import io.jsonwebtoken.codec.Decoder;
|
||||||
import io.jsonwebtoken.impl.DefaultClock;
|
import io.jsonwebtoken.impl.DefaultClock;
|
||||||
|
import io.jsonwebtoken.io.Deserializer;
|
||||||
|
|
||||||
import java.security.Key;
|
import java.security.Key;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A parser for reading JWT strings, used to convert them into a {@link Jwt} object representing the expanded JWT.
|
* A parser for reading JWT strings, used to convert them into a {@link Jwt} object representing the expanded JWT.
|
||||||
|
@ -282,6 +284,22 @@ public interface JwtParser {
|
||||||
*/
|
*/
|
||||||
JwtParser base64UrlDecodeWith(Decoder<String, byte[]> base64UrlDecoder);
|
JwtParser base64UrlDecodeWith(Decoder<String, byte[]> base64UrlDecoder);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Uses the specified deserializer to convert JSON Strings (UTF-8 byte arrays) into Java Map objects. This is
|
||||||
|
* used by the parser after Base64Url-decoding to convert JWT/JWS/JWT JSON headers and claims into Java Map
|
||||||
|
* objects.
|
||||||
|
*
|
||||||
|
* <p>If this method is not called, JJWT will use whatever deserializer it can find at runtime, checking for the
|
||||||
|
* presence of well-known implementations such Jackson, Gson, and org.json. If one of these is not found
|
||||||
|
* in the runtime classpath, an exception will be thrown when one of the various {@code parse}* methods is
|
||||||
|
* invoked.</p>
|
||||||
|
*
|
||||||
|
* @param deserializer the deserializer to use when converting JSON Strings (UTF-8 byte arrays) into Map objects.
|
||||||
|
* @return the builder for method chaining.
|
||||||
|
* @since 0.10.0
|
||||||
|
*/
|
||||||
|
JwtParser deserializeJsonWith(Deserializer<Map<String,?>> deserializer);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns {@code true} if the specified JWT compact string represents a signed JWT (aka a 'JWS'), {@code false}
|
* Returns {@code true} if the specified JWT compact string represents a signed JWT (aka a 'JWS'), {@code false}
|
||||||
* otherwise.
|
* otherwise.
|
||||||
|
|
|
@ -71,7 +71,7 @@ public class DefaultClaims extends JwtMap implements Claims {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Claims setExpiration(Date exp) {
|
public Claims setExpiration(Date exp) {
|
||||||
setDate(Claims.EXPIRATION, exp);
|
setDateAsSeconds(Claims.EXPIRATION, exp);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -82,7 +82,7 @@ public class DefaultClaims extends JwtMap implements Claims {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Claims setNotBefore(Date nbf) {
|
public Claims setNotBefore(Date nbf) {
|
||||||
setDate(Claims.NOT_BEFORE, nbf);
|
setDateAsSeconds(Claims.NOT_BEFORE, nbf);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -93,7 +93,7 @@ public class DefaultClaims extends JwtMap implements Claims {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Claims setIssuedAt(Date iat) {
|
public Claims setIssuedAt(Date iat) {
|
||||||
setDate(Claims.ISSUED_AT, iat);
|
setDateAsSeconds(Claims.ISSUED_AT, iat);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -108,25 +108,35 @@ public class DefaultClaims extends JwtMap implements Claims {
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @since 0.10.0
|
||||||
|
*/
|
||||||
|
private static boolean isSpecDate(String claimName) {
|
||||||
|
return Claims.EXPIRATION.equals(claimName) ||
|
||||||
|
Claims.ISSUED_AT.equals(claimName) ||
|
||||||
|
Claims.NOT_BEFORE.equals(claimName);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public <T> T get(String claimName, Class<T> requiredType) {
|
public <T> T get(String claimName, Class<T> requiredType) {
|
||||||
Object value = get(claimName);
|
|
||||||
if (value == null) { return null; }
|
|
||||||
|
|
||||||
if (Claims.EXPIRATION.equals(claimName) ||
|
Object value = get(claimName);
|
||||||
Claims.ISSUED_AT.equals(claimName) ||
|
if (value == null) {
|
||||||
Claims.NOT_BEFORE.equals(claimName)
|
return null;
|
||||||
) {
|
}
|
||||||
value = getDate(claimName);
|
|
||||||
|
if (Date.class.equals(requiredType)) {
|
||||||
|
if (isSpecDate(claimName)) {
|
||||||
|
value = toSpecDate(value, claimName);
|
||||||
|
} else {
|
||||||
|
value = toDate(value, claimName);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return castClaimValue(value, requiredType);
|
return castClaimValue(value, requiredType);
|
||||||
}
|
}
|
||||||
|
|
||||||
private <T> T castClaimValue(Object value, Class<T> requiredType) {
|
private <T> T castClaimValue(Object value, Class<T> requiredType) {
|
||||||
if (requiredType == Date.class && value instanceof Long) {
|
|
||||||
value = new Date((Long)value);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (value instanceof Integer) {
|
if (value instanceof Integer) {
|
||||||
int intValue = (Integer) value;
|
int intValue = (Integer) value;
|
||||||
|
|
|
@ -15,8 +15,6 @@
|
||||||
*/
|
*/
|
||||||
package io.jsonwebtoken.impl;
|
package io.jsonwebtoken.impl;
|
||||||
|
|
||||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
|
||||||
import io.jsonwebtoken.Claims;
|
import io.jsonwebtoken.Claims;
|
||||||
import io.jsonwebtoken.CompressionCodec;
|
import io.jsonwebtoken.CompressionCodec;
|
||||||
import io.jsonwebtoken.Header;
|
import io.jsonwebtoken.Header;
|
||||||
|
@ -29,7 +27,11 @@ import io.jsonwebtoken.codec.Decoder;
|
||||||
import io.jsonwebtoken.codec.Encoder;
|
import io.jsonwebtoken.codec.Encoder;
|
||||||
import io.jsonwebtoken.impl.crypto.DefaultJwtSigner;
|
import io.jsonwebtoken.impl.crypto.DefaultJwtSigner;
|
||||||
import io.jsonwebtoken.impl.crypto.JwtSigner;
|
import io.jsonwebtoken.impl.crypto.JwtSigner;
|
||||||
|
import io.jsonwebtoken.io.SerializationException;
|
||||||
|
import io.jsonwebtoken.io.Serializer;
|
||||||
|
import io.jsonwebtoken.io.impl.InstanceLocator;
|
||||||
import io.jsonwebtoken.lang.Assert;
|
import io.jsonwebtoken.lang.Assert;
|
||||||
|
import io.jsonwebtoken.lang.Classes;
|
||||||
import io.jsonwebtoken.lang.Collections;
|
import io.jsonwebtoken.lang.Collections;
|
||||||
import io.jsonwebtoken.lang.Strings;
|
import io.jsonwebtoken.lang.Strings;
|
||||||
|
|
||||||
|
@ -40,8 +42,6 @@ import java.util.Map;
|
||||||
|
|
||||||
public class DefaultJwtBuilder implements JwtBuilder {
|
public class DefaultJwtBuilder implements JwtBuilder {
|
||||||
|
|
||||||
private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
|
|
||||||
|
|
||||||
private Header header;
|
private Header header;
|
||||||
private Claims claims;
|
private Claims claims;
|
||||||
private String payload;
|
private String payload;
|
||||||
|
@ -49,10 +49,19 @@ public class DefaultJwtBuilder implements JwtBuilder {
|
||||||
private SignatureAlgorithm algorithm;
|
private SignatureAlgorithm algorithm;
|
||||||
private Key key;
|
private Key key;
|
||||||
|
|
||||||
|
private Serializer<Map<String,?>> serializer;
|
||||||
|
|
||||||
private Encoder<byte[], String> base64UrlEncoder = Encoder.BASE64URL;
|
private Encoder<byte[], String> base64UrlEncoder = Encoder.BASE64URL;
|
||||||
|
|
||||||
private CompressionCodec compressionCodec;
|
private CompressionCodec compressionCodec;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public JwtBuilder serializeToJsonWith(Serializer<Map<String,?>> serializer) {
|
||||||
|
Assert.notNull(serializer, "Serializer cannot be null.");
|
||||||
|
this.serializer = serializer;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public JwtBuilder base64UrlEncodeWith(Encoder<byte[], String> base64UrlEncoder) {
|
public JwtBuilder base64UrlEncodeWith(Encoder<byte[], String> base64UrlEncoder) {
|
||||||
Assert.notNull(base64UrlEncoder, "base64UrlEncoder cannot be null.");
|
Assert.notNull(base64UrlEncoder, "base64UrlEncoder cannot be null.");
|
||||||
|
@ -270,6 +279,14 @@ public class DefaultJwtBuilder implements JwtBuilder {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String compact() {
|
public String compact() {
|
||||||
|
|
||||||
|
if (this.serializer == null) {
|
||||||
|
//try to find one based on the runtime environment:
|
||||||
|
InstanceLocator<Serializer<Map<String,?>>> locator =
|
||||||
|
Classes.newInstance("io.jsonwebtoken.io.impl.RuntimeClasspathSerializerLocator");
|
||||||
|
this.serializer = locator.getInstance();
|
||||||
|
}
|
||||||
|
|
||||||
if (payload == null && Collections.isEmpty(claims)) {
|
if (payload == null && Collections.isEmpty(claims)) {
|
||||||
throw new IllegalStateException("Either 'payload' or 'claims' must be specified.");
|
throw new IllegalStateException("Either 'payload' or 'claims' must be specified.");
|
||||||
}
|
}
|
||||||
|
@ -304,8 +321,8 @@ public class DefaultJwtBuilder implements JwtBuilder {
|
||||||
byte[] bytes;
|
byte[] bytes;
|
||||||
try {
|
try {
|
||||||
bytes = this.payload != null ? payload.getBytes(Strings.UTF_8) : toJson(claims);
|
bytes = this.payload != null ? payload.getBytes(Strings.UTF_8) : toJson(claims);
|
||||||
} catch (JsonProcessingException e) {
|
} catch (SerializationException e) {
|
||||||
throw new IllegalArgumentException("Unable to serialize claims object to json.");
|
throw new IllegalArgumentException("Unable to serialize claims object to json: " + e.getMessage(), e);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (compressionCodec != null) {
|
if (compressionCodec != null) {
|
||||||
|
@ -339,18 +356,25 @@ public class DefaultJwtBuilder implements JwtBuilder {
|
||||||
return new DefaultJwtSigner(alg, key, base64UrlEncoder);
|
return new DefaultJwtSigner(alg, key, base64UrlEncoder);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Deprecated // remove before 1.0 - call the serializer and base64UrlEncoder directly
|
||||||
protected String base64UrlEncode(Object o, String errMsg) {
|
protected String base64UrlEncode(Object o, String errMsg) {
|
||||||
|
Assert.isInstanceOf(Map.class, o, "object argument must be a map.");
|
||||||
|
Map m = (Map)o;
|
||||||
byte[] bytes;
|
byte[] bytes;
|
||||||
try {
|
try {
|
||||||
bytes = toJson(o);
|
bytes = toJson(m);
|
||||||
} catch (JsonProcessingException e) {
|
} catch (SerializationException e) {
|
||||||
throw new IllegalStateException(errMsg, e);
|
throw new IllegalStateException(errMsg, e);
|
||||||
}
|
}
|
||||||
|
|
||||||
return base64UrlEncoder.encode(bytes);
|
return base64UrlEncoder.encode(bytes);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected byte[] toJson(Object object) throws JsonProcessingException {
|
@SuppressWarnings("unchecked")
|
||||||
return OBJECT_MAPPER.writeValueAsBytes(object);
|
@Deprecated //remove before 1.0 - call the serializer directly
|
||||||
|
protected byte[] toJson(Object object) throws SerializationException {
|
||||||
|
Assert.isInstanceOf(Map.class, object, "object argument must be a map.");
|
||||||
|
Map m = (Map)object;
|
||||||
|
return serializer.serialize(m);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,7 +15,6 @@
|
||||||
*/
|
*/
|
||||||
package io.jsonwebtoken.impl;
|
package io.jsonwebtoken.impl;
|
||||||
|
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
|
||||||
import io.jsonwebtoken.ClaimJwtException;
|
import io.jsonwebtoken.ClaimJwtException;
|
||||||
import io.jsonwebtoken.Claims;
|
import io.jsonwebtoken.Claims;
|
||||||
import io.jsonwebtoken.Clock;
|
import io.jsonwebtoken.Clock;
|
||||||
|
@ -42,26 +41,25 @@ import io.jsonwebtoken.codec.Decoder;
|
||||||
import io.jsonwebtoken.impl.compression.DefaultCompressionCodecResolver;
|
import io.jsonwebtoken.impl.compression.DefaultCompressionCodecResolver;
|
||||||
import io.jsonwebtoken.impl.crypto.DefaultJwtSignatureValidator;
|
import io.jsonwebtoken.impl.crypto.DefaultJwtSignatureValidator;
|
||||||
import io.jsonwebtoken.impl.crypto.JwtSignatureValidator;
|
import io.jsonwebtoken.impl.crypto.JwtSignatureValidator;
|
||||||
|
import io.jsonwebtoken.io.DeserializationException;
|
||||||
|
import io.jsonwebtoken.io.Deserializer;
|
||||||
|
import io.jsonwebtoken.io.impl.InstanceLocator;
|
||||||
import io.jsonwebtoken.lang.Assert;
|
import io.jsonwebtoken.lang.Assert;
|
||||||
|
import io.jsonwebtoken.lang.Classes;
|
||||||
|
import io.jsonwebtoken.lang.DateFormats;
|
||||||
import io.jsonwebtoken.lang.Objects;
|
import io.jsonwebtoken.lang.Objects;
|
||||||
import io.jsonwebtoken.lang.Strings;
|
import io.jsonwebtoken.lang.Strings;
|
||||||
|
|
||||||
import javax.crypto.spec.SecretKeySpec;
|
import javax.crypto.spec.SecretKeySpec;
|
||||||
import java.io.IOException;
|
|
||||||
import java.security.Key;
|
import java.security.Key;
|
||||||
import java.text.SimpleDateFormat;
|
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
public class DefaultJwtParser implements JwtParser {
|
public class DefaultJwtParser implements JwtParser {
|
||||||
|
|
||||||
//don't need millis since JWT date fields are only second granularity:
|
|
||||||
private static final String ISO_8601_FORMAT = "yyyy-MM-dd'T'HH:mm:ss'Z'";
|
|
||||||
private static final int MILLISECONDS_PER_SECOND = 1000;
|
private static final int MILLISECONDS_PER_SECOND = 1000;
|
||||||
|
|
||||||
private ObjectMapper objectMapper = new ObjectMapper();
|
|
||||||
|
|
||||||
private byte[] keyBytes;
|
private byte[] keyBytes;
|
||||||
|
|
||||||
private Key key;
|
private Key key;
|
||||||
|
@ -72,12 +70,21 @@ public class DefaultJwtParser implements JwtParser {
|
||||||
|
|
||||||
private Decoder<String, byte[]> base64UrlDecoder = Decoder.BASE64URL;
|
private Decoder<String, byte[]> base64UrlDecoder = Decoder.BASE64URL;
|
||||||
|
|
||||||
|
private Deserializer<Map<String, ?>> deserializer;
|
||||||
|
|
||||||
private Claims expectedClaims = new DefaultClaims();
|
private Claims expectedClaims = new DefaultClaims();
|
||||||
|
|
||||||
private Clock clock = DefaultClock.INSTANCE;
|
private Clock clock = DefaultClock.INSTANCE;
|
||||||
|
|
||||||
private long allowedClockSkewMillis = 0;
|
private long allowedClockSkewMillis = 0;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public JwtParser deserializeJsonWith(Deserializer<Map<String, ?>> deserializer) {
|
||||||
|
Assert.notNull(deserializer, "deserializer cannot be null.");
|
||||||
|
this.deserializer = deserializer;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public JwtParser base64UrlDecodeWith(Decoder<String, byte[]> base64UrlDecoder) {
|
public JwtParser base64UrlDecodeWith(Decoder<String, byte[]> base64UrlDecoder) {
|
||||||
Assert.notNull(base64UrlDecoder, "base64UrlDecoder cannot be null.");
|
Assert.notNull(base64UrlDecoder, "base64UrlDecoder cannot be null.");
|
||||||
|
@ -210,6 +217,13 @@ public class DefaultJwtParser implements JwtParser {
|
||||||
@Override
|
@Override
|
||||||
public Jwt parse(String jwt) throws ExpiredJwtException, MalformedJwtException, SignatureException {
|
public Jwt parse(String jwt) throws ExpiredJwtException, MalformedJwtException, SignatureException {
|
||||||
|
|
||||||
|
if (this.deserializer == null) {
|
||||||
|
//try to find one based on the runtime environment:
|
||||||
|
InstanceLocator<Deserializer<Map<String, ?>>> locator =
|
||||||
|
Classes.newInstance("io.jsonwebtoken.io.impl.RuntimeClasspathDeserializerLocator");
|
||||||
|
this.deserializer = locator.getInstance();
|
||||||
|
}
|
||||||
|
|
||||||
Assert.hasText(jwt, "JWT String argument cannot be null or empty.");
|
Assert.hasText(jwt, "JWT String argument cannot be null or empty.");
|
||||||
|
|
||||||
String base64UrlEncodedHeader = null;
|
String base64UrlEncodedHeader = null;
|
||||||
|
@ -260,7 +274,7 @@ public class DefaultJwtParser implements JwtParser {
|
||||||
if (base64UrlEncodedHeader != null) {
|
if (base64UrlEncodedHeader != null) {
|
||||||
byte[] bytes = base64UrlDecoder.decode(base64UrlEncodedHeader);
|
byte[] bytes = base64UrlDecoder.decode(base64UrlEncodedHeader);
|
||||||
String origValue = new String(bytes, Strings.UTF_8);
|
String origValue = new String(bytes, Strings.UTF_8);
|
||||||
Map<String, Object> m = readValue(origValue);
|
Map<String, Object> m = (Map<String, Object>) readValue(origValue);
|
||||||
|
|
||||||
if (base64UrlEncodedDigest != null) {
|
if (base64UrlEncodedDigest != null) {
|
||||||
header = new DefaultJwsHeader(m);
|
header = new DefaultJwsHeader(m);
|
||||||
|
@ -281,7 +295,7 @@ public class DefaultJwtParser implements JwtParser {
|
||||||
Claims claims = null;
|
Claims claims = null;
|
||||||
|
|
||||||
if (payload.charAt(0) == '{' && payload.charAt(payload.length() - 1) == '}') { //likely to be json, parse it:
|
if (payload.charAt(0) == '{' && payload.charAt(payload.length() - 1) == '}') { //likely to be json, parse it:
|
||||||
Map<String, Object> claimsMap = readValue(payload);
|
Map<String, Object> claimsMap = (Map<String, Object>) readValue(payload);
|
||||||
claims = new DefaultClaims(claimsMap);
|
claims = new DefaultClaims(claimsMap);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -369,8 +383,6 @@ public class DefaultJwtParser implements JwtParser {
|
||||||
//since 0.3:
|
//since 0.3:
|
||||||
if (claims != null) {
|
if (claims != null) {
|
||||||
|
|
||||||
SimpleDateFormat sdf;
|
|
||||||
|
|
||||||
final Date now = this.clock.now();
|
final Date now = this.clock.now();
|
||||||
long nowTime = now.getTime();
|
long nowTime = now.getTime();
|
||||||
|
|
||||||
|
@ -382,9 +394,8 @@ public class DefaultJwtParser implements JwtParser {
|
||||||
long maxTime = nowTime - this.allowedClockSkewMillis;
|
long maxTime = nowTime - this.allowedClockSkewMillis;
|
||||||
Date max = allowSkew ? new Date(maxTime) : now;
|
Date max = allowSkew ? new Date(maxTime) : now;
|
||||||
if (max.after(exp)) {
|
if (max.after(exp)) {
|
||||||
sdf = new SimpleDateFormat(ISO_8601_FORMAT);
|
String expVal = DateFormats.formatIso8601(exp, false);
|
||||||
String expVal = sdf.format(exp);
|
String nowVal = DateFormats.formatIso8601(now, false);
|
||||||
String nowVal = sdf.format(now);
|
|
||||||
|
|
||||||
long differenceMillis = maxTime - exp.getTime();
|
long differenceMillis = maxTime - exp.getTime();
|
||||||
|
|
||||||
|
@ -403,9 +414,8 @@ public class DefaultJwtParser implements JwtParser {
|
||||||
long minTime = nowTime + this.allowedClockSkewMillis;
|
long minTime = nowTime + this.allowedClockSkewMillis;
|
||||||
Date min = allowSkew ? new Date(minTime) : now;
|
Date min = allowSkew ? new Date(minTime) : now;
|
||||||
if (min.before(nbf)) {
|
if (min.before(nbf)) {
|
||||||
sdf = new SimpleDateFormat(ISO_8601_FORMAT);
|
String nbfVal = DateFormats.formatIso8601(nbf, false);
|
||||||
String nbfVal = sdf.format(nbf);
|
String nowVal = DateFormats.formatIso8601(now, false);
|
||||||
String nowVal = sdf.format(now);
|
|
||||||
|
|
||||||
long differenceMillis = nbf.getTime() - minTime;
|
long differenceMillis = nbf.getTime() - minTime;
|
||||||
|
|
||||||
|
@ -423,27 +433,37 @@ public class DefaultJwtParser implements JwtParser {
|
||||||
Object body = claims != null ? claims : payload;
|
Object body = claims != null ? claims : payload;
|
||||||
|
|
||||||
if (base64UrlEncodedDigest != null) {
|
if (base64UrlEncodedDigest != null) {
|
||||||
return new DefaultJws<Object>((JwsHeader) header, body, base64UrlEncodedDigest);
|
return new DefaultJws<>((JwsHeader) header, body, base64UrlEncodedDigest);
|
||||||
} else {
|
} else {
|
||||||
return new DefaultJwt<Object>(header, body);
|
return new DefaultJwt<>(header, body);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @since 0.10.0
|
||||||
|
*/
|
||||||
|
private static Object normalize(Object o) {
|
||||||
|
if (o instanceof Integer) {
|
||||||
|
o = ((Integer)o).longValue();
|
||||||
|
}
|
||||||
|
return o;
|
||||||
|
}
|
||||||
|
|
||||||
private void validateExpectedClaims(Header header, Claims claims) {
|
private void validateExpectedClaims(Header header, Claims claims) {
|
||||||
|
|
||||||
for (String expectedClaimName : expectedClaims.keySet()) {
|
for (String expectedClaimName : expectedClaims.keySet()) {
|
||||||
|
|
||||||
Object expectedClaimValue = expectedClaims.get(expectedClaimName);
|
Object expectedClaimValue = normalize(expectedClaims.get(expectedClaimName));
|
||||||
Object actualClaimValue = claims.get(expectedClaimName);
|
Object actualClaimValue = normalize(claims.get(expectedClaimName));
|
||||||
|
|
||||||
if (Claims.ISSUED_AT.equals(expectedClaimName) || Claims.EXPIRATION.equals(expectedClaimName) ||
|
if (expectedClaimValue instanceof Date) {
|
||||||
Claims.NOT_BEFORE.equals(expectedClaimName)) {
|
try {
|
||||||
|
|
||||||
expectedClaimValue = expectedClaims.get(expectedClaimName, Date.class);
|
|
||||||
actualClaimValue = claims.get(expectedClaimName, Date.class);
|
actualClaimValue = claims.get(expectedClaimName, Date.class);
|
||||||
|
} catch (Exception e) {
|
||||||
} else if (expectedClaimValue instanceof Date && actualClaimValue instanceof Long) {
|
String msg = "JWT Claim '" + expectedClaimName + "' was expected to be a Date, but its value " +
|
||||||
|
"cannot be converted to a Date using current heuristics. Value: " + actualClaimValue;
|
||||||
actualClaimValue = new Date((Long) actualClaimValue);
|
throw new IncorrectClaimException(header, claims, msg);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
InvalidClaimException invalidClaimException = null;
|
InvalidClaimException invalidClaimException = null;
|
||||||
|
@ -553,10 +573,11 @@ public class DefaultJwtParser implements JwtParser {
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
protected Map<String, Object> readValue(String val) {
|
protected Map<String, ?> readValue(String val) {
|
||||||
try {
|
try {
|
||||||
return objectMapper.readValue(val, Map.class);
|
byte[] bytes = val.getBytes(Strings.UTF_8);
|
||||||
} catch (IOException e) {
|
return deserializer.deserialize(bytes);
|
||||||
|
} catch (DeserializationException e) {
|
||||||
throw new MalformedJwtException("Unable to read JSON value: " + val, e);
|
throw new MalformedJwtException("Unable to read JSON value: " + val, e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,7 +16,10 @@
|
||||||
package io.jsonwebtoken.impl;
|
package io.jsonwebtoken.impl;
|
||||||
|
|
||||||
import io.jsonwebtoken.lang.Assert;
|
import io.jsonwebtoken.lang.Assert;
|
||||||
|
import io.jsonwebtoken.lang.DateFormats;
|
||||||
|
|
||||||
|
import java.text.ParseException;
|
||||||
|
import java.util.Calendar;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
import java.util.LinkedHashMap;
|
import java.util.LinkedHashMap;
|
||||||
|
@ -46,24 +49,56 @@ public class JwtMap implements Map<String,Object> {
|
||||||
return null;
|
return null;
|
||||||
} else if (v instanceof Date) {
|
} else if (v instanceof Date) {
|
||||||
return (Date) v;
|
return (Date) v;
|
||||||
|
} else if (v instanceof Calendar) { //since 0.10.0
|
||||||
|
return ((Calendar) v).getTime();
|
||||||
|
} else if (v instanceof Number) {
|
||||||
|
//assume millis:
|
||||||
|
long millis = ((Number) v).longValue();
|
||||||
|
return new Date(millis);
|
||||||
|
} else if (v instanceof String) {
|
||||||
|
return parseIso8601Date((String) v, name); //ISO-8601 parsing since 0.10.0
|
||||||
|
} else {
|
||||||
|
throw new IllegalStateException("Cannot create Date from '" + name + "' value '" + v + "'.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @since 0.10.0
|
||||||
|
*/
|
||||||
|
private static Date parseIso8601Date(String s, String name) throws IllegalArgumentException {
|
||||||
|
try {
|
||||||
|
return DateFormats.parseIso8601Date(s);
|
||||||
|
} catch (ParseException e) {
|
||||||
|
String msg = "'" + name + "' value does not appear to be ISO-8601-formatted: " + s;
|
||||||
|
throw new IllegalArgumentException(msg, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @since 0.10.0
|
||||||
|
*/
|
||||||
|
protected static Date toSpecDate(Object v, String name) {
|
||||||
|
if (v == null) {
|
||||||
|
return null;
|
||||||
} else if (v instanceof Number) {
|
} else if (v instanceof Number) {
|
||||||
// https://github.com/jwtk/jjwt/issues/122:
|
// https://github.com/jwtk/jjwt/issues/122:
|
||||||
// The JWT RFC *mandates* NumericDate values are represented as seconds.
|
// The JWT RFC *mandates* NumericDate values are represented as seconds.
|
||||||
// Because Because java.util.Date requires milliseconds, we need to multiply by 1000:
|
// Because Because java.util.Date requires milliseconds, we need to multiply by 1000:
|
||||||
long seconds = ((Number) v).longValue();
|
long seconds = ((Number) v).longValue();
|
||||||
long millis = seconds * 1000;
|
v = seconds * 1000;
|
||||||
return new Date(millis);
|
|
||||||
} else if (v instanceof String) {
|
} else if (v instanceof String) {
|
||||||
// https://github.com/jwtk/jjwt/issues/122
|
// https://github.com/jwtk/jjwt/issues/122
|
||||||
// The JWT RFC *mandates* NumericDate values are represented as seconds.
|
// The JWT RFC *mandates* NumericDate values are represented as seconds.
|
||||||
// Because Because java.util.Date requires milliseconds, we need to multiply by 1000:
|
// Because Because java.util.Date requires milliseconds, we need to multiply by 1000:
|
||||||
|
try {
|
||||||
long seconds = Long.parseLong((String) v);
|
long seconds = Long.parseLong((String) v);
|
||||||
long millis = seconds * 1000;
|
v = seconds * 1000;
|
||||||
return new Date(millis);
|
} catch (NumberFormatException ignored) {
|
||||||
} else {
|
|
||||||
throw new IllegalStateException("Cannot convert '" + name + "' value [" + v + "] to Date instance.");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
//v would have been normalized to milliseconds if it was a number value, so perform normal date conversion:
|
||||||
|
return toDate(v, name);
|
||||||
|
}
|
||||||
|
|
||||||
protected void setValue(String name, Object v) {
|
protected void setValue(String name, Object v) {
|
||||||
if (v == null) {
|
if (v == null) {
|
||||||
|
@ -73,12 +108,17 @@ public class JwtMap implements Map<String,Object> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected Date getDate(String name) {
|
@Deprecated //remove just before 1.0.0
|
||||||
Object v = map.get(name);
|
protected void setDate(String name, Date d) {
|
||||||
return toDate(v, name);
|
if (d == null) {
|
||||||
|
map.remove(name);
|
||||||
|
} else {
|
||||||
|
long seconds = d.getTime() / 1000;
|
||||||
|
map.put(name, seconds);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void setDate(String name, Date d) {
|
protected void setDateAsSeconds(String name, Date d) {
|
||||||
if (d == null) {
|
if (d == null) {
|
||||||
map.remove(name);
|
map.remove(name);
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -0,0 +1,12 @@
|
||||||
|
package io.jsonwebtoken.io;
|
||||||
|
|
||||||
|
public class DeserializationException extends SerialException {
|
||||||
|
|
||||||
|
public DeserializationException(String msg) {
|
||||||
|
super(msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
public DeserializationException(String message, Throwable cause) {
|
||||||
|
super(message, cause);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,6 @@
|
||||||
|
package io.jsonwebtoken.io;
|
||||||
|
|
||||||
|
public interface Deserializer<T> {
|
||||||
|
|
||||||
|
T deserialize(byte[] bytes) throws DeserializationException;
|
||||||
|
}
|
|
@ -0,0 +1,14 @@
|
||||||
|
package io.jsonwebtoken.io;
|
||||||
|
|
||||||
|
import io.jsonwebtoken.JwtException;
|
||||||
|
|
||||||
|
public class IOException extends JwtException {
|
||||||
|
|
||||||
|
public IOException(String msg) {
|
||||||
|
super(msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
public IOException(String message, Throwable cause) {
|
||||||
|
super(message, cause);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,12 @@
|
||||||
|
package io.jsonwebtoken.io;
|
||||||
|
|
||||||
|
public class SerialException extends IOException {
|
||||||
|
|
||||||
|
public SerialException(String msg) {
|
||||||
|
super(msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
public SerialException(String message, Throwable cause) {
|
||||||
|
super(message, cause);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,12 @@
|
||||||
|
package io.jsonwebtoken.io;
|
||||||
|
|
||||||
|
public class SerializationException extends SerialException {
|
||||||
|
|
||||||
|
public SerializationException(String msg) {
|
||||||
|
super(msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
public SerializationException(String message, Throwable cause) {
|
||||||
|
super(message, cause);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,7 @@
|
||||||
|
package io.jsonwebtoken.io;
|
||||||
|
|
||||||
|
public interface Serializer<T> {
|
||||||
|
|
||||||
|
byte[] serialize(T t) throws SerializationException;
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,6 @@
|
||||||
|
package io.jsonwebtoken.io.impl;
|
||||||
|
|
||||||
|
public interface InstanceLocator<T> {
|
||||||
|
|
||||||
|
T getInstance();
|
||||||
|
}
|
|
@ -0,0 +1,48 @@
|
||||||
|
package io.jsonwebtoken.io.impl;
|
||||||
|
|
||||||
|
import io.jsonwebtoken.io.Deserializer;
|
||||||
|
import io.jsonwebtoken.lang.Assert;
|
||||||
|
import io.jsonwebtoken.lang.Classes;
|
||||||
|
|
||||||
|
import java.util.concurrent.atomic.AtomicReference;
|
||||||
|
|
||||||
|
public class RuntimeClasspathDeserializerLocator<T> implements InstanceLocator<Deserializer<T>> {
|
||||||
|
|
||||||
|
private static final AtomicReference<Deserializer> DESERIALIZER = new AtomicReference<>();
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
@Override
|
||||||
|
public Deserializer<T> getInstance() {
|
||||||
|
Deserializer<T> deserializer = DESERIALIZER.get();
|
||||||
|
if (deserializer == null) {
|
||||||
|
deserializer = locate();
|
||||||
|
Assert.state(deserializer != null, "locate() cannot return null.");
|
||||||
|
if (!compareAndSet(deserializer)) {
|
||||||
|
deserializer = DESERIALIZER.get();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Assert.state(deserializer != null, "deserializer cannot be null.");
|
||||||
|
return deserializer;
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("WeakerAccess") //to allow testing override
|
||||||
|
protected Deserializer<T> locate() {
|
||||||
|
if (isAvailable("com.fasterxml.jackson.databind.ObjectMapper")) {
|
||||||
|
return Classes.newInstance("io.jsonwebtoken.io.impl.jackson.JacksonDeserializer");
|
||||||
|
} else if (isAvailable("org.json.JSONObject")) {
|
||||||
|
return Classes.newInstance("io.jsonwebtoken.io.impl.orgjson.OrgJsonDeserializer");
|
||||||
|
} else {
|
||||||
|
throw new IllegalStateException("Unable to discover any JSON Deserializer implementations on the classpath.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("WeakerAccess") //to allow testing override
|
||||||
|
protected boolean compareAndSet(Deserializer<T> d) {
|
||||||
|
return DESERIALIZER.compareAndSet(null, d);
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("WeakerAccess") //to allow testing override
|
||||||
|
protected boolean isAvailable(String fqcn) {
|
||||||
|
return Classes.isAvailable(fqcn);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,48 @@
|
||||||
|
package io.jsonwebtoken.io.impl;
|
||||||
|
|
||||||
|
import io.jsonwebtoken.io.Serializer;
|
||||||
|
import io.jsonwebtoken.lang.Assert;
|
||||||
|
import io.jsonwebtoken.lang.Classes;
|
||||||
|
|
||||||
|
import java.util.concurrent.atomic.AtomicReference;
|
||||||
|
|
||||||
|
public class RuntimeClasspathSerializerLocator implements InstanceLocator<Serializer> {
|
||||||
|
|
||||||
|
private static final AtomicReference<Serializer<Object>> SERIALIZER = new AtomicReference<>();
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
@Override
|
||||||
|
public Serializer<Object> getInstance() {
|
||||||
|
Serializer<Object> serializer = SERIALIZER.get();
|
||||||
|
if (serializer == null) {
|
||||||
|
serializer = locate();
|
||||||
|
Assert.state(serializer != null, "locate() cannot return null.");
|
||||||
|
if (!compareAndSet(serializer)) {
|
||||||
|
serializer = SERIALIZER.get();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Assert.state(serializer != null, "serializer cannot be null.");
|
||||||
|
return serializer;
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("WeakerAccess") //to allow testing override
|
||||||
|
protected Serializer<Object> locate() {
|
||||||
|
if (isAvailable("com.fasterxml.jackson.databind.ObjectMapper")) {
|
||||||
|
return Classes.newInstance("io.jsonwebtoken.io.impl.jackson.JacksonSerializer");
|
||||||
|
} else if (isAvailable("org.json.JSONObject")) {
|
||||||
|
return Classes.newInstance("io.jsonwebtoken.io.impl.orgjson.OrgJsonSerializer");
|
||||||
|
} else {
|
||||||
|
throw new IllegalStateException("Unable to discover any JSON Serializer implementations on the classpath.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("WeakerAccess") //to allow testing override
|
||||||
|
protected boolean compareAndSet(Serializer<Object> s) {
|
||||||
|
return SERIALIZER.compareAndSet(null, s);
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("WeakerAccess") //to allow testing override
|
||||||
|
protected boolean isAvailable(String fqcn) {
|
||||||
|
return Classes.isAvailable(fqcn);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,47 @@
|
||||||
|
package io.jsonwebtoken.io.impl.jackson;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
import io.jsonwebtoken.io.DeserializationException;
|
||||||
|
import io.jsonwebtoken.io.Deserializer;
|
||||||
|
import io.jsonwebtoken.lang.Assert;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
@SuppressWarnings("unused") //used via reflection by RuntimeClasspathDeserializerLocator
|
||||||
|
public class JacksonDeserializer<T> implements Deserializer<T> {
|
||||||
|
|
||||||
|
private final Class<T> returnType;
|
||||||
|
private final ObjectMapper objectMapper;
|
||||||
|
|
||||||
|
@SuppressWarnings("unused") //used via reflection by RuntimeClasspathDeserializerLocator
|
||||||
|
public JacksonDeserializer() {
|
||||||
|
this(JacksonSerializer.DEFAULT_OBJECT_MAPPER);
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings({"unchecked", "WeakerAccess", "unused"}) // for end-users providing a custom ObjectMapper
|
||||||
|
public JacksonDeserializer(ObjectMapper objectMapper) {
|
||||||
|
this(objectMapper, (Class<T>) Map.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
|
||||||
|
public T deserialize(byte[] bytes) throws DeserializationException {
|
||||||
|
try {
|
||||||
|
return readValue(bytes);
|
||||||
|
} catch (IOException e) {
|
||||||
|
String msg = "Unable to deserialize bytes into a " + returnType.getName() + " instance: " + e.getMessage();
|
||||||
|
throw new DeserializationException(msg, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected T readValue(byte[] bytes) throws IOException {
|
||||||
|
return objectMapper.readValue(bytes, returnType);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,41 @@
|
||||||
|
package io.jsonwebtoken.io.impl.jackson;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
import io.jsonwebtoken.io.SerializationException;
|
||||||
|
import io.jsonwebtoken.io.Serializer;
|
||||||
|
import io.jsonwebtoken.lang.Assert;
|
||||||
|
|
||||||
|
public class JacksonSerializer<T> implements Serializer<T> {
|
||||||
|
|
||||||
|
static final ObjectMapper DEFAULT_OBJECT_MAPPER = new ObjectMapper();
|
||||||
|
|
||||||
|
private final ObjectMapper objectMapper;
|
||||||
|
|
||||||
|
@SuppressWarnings("unused") //used via reflection by RuntimeClasspathDeserializerLocator
|
||||||
|
public JacksonSerializer() {
|
||||||
|
this(DEFAULT_OBJECT_MAPPER);
|
||||||
|
}
|
||||||
|
|
||||||
|
@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;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public byte[] serialize(T t) throws SerializationException {
|
||||||
|
Assert.notNull(t, "Object to serialize cannot be null.");
|
||||||
|
try {
|
||||||
|
return writeValueAsBytes(t);
|
||||||
|
} catch (JsonProcessingException e) {
|
||||||
|
String msg = "Unable to serialize object: " + e.getMessage();
|
||||||
|
throw new SerializationException(msg, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("WeakerAccess") //for testing
|
||||||
|
protected byte[] writeValueAsBytes(T t) throws JsonProcessingException {
|
||||||
|
return this.objectMapper.writeValueAsBytes(t);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,88 @@
|
||||||
|
package io.jsonwebtoken.io.impl.orgjson;
|
||||||
|
|
||||||
|
import io.jsonwebtoken.io.DeserializationException;
|
||||||
|
import io.jsonwebtoken.io.Deserializer;
|
||||||
|
import io.jsonwebtoken.lang.Assert;
|
||||||
|
import io.jsonwebtoken.lang.Strings;
|
||||||
|
import org.json.JSONArray;
|
||||||
|
import org.json.JSONException;
|
||||||
|
import org.json.JSONObject;
|
||||||
|
import org.json.JSONTokener;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
public class OrgJsonDeserializer implements Deserializer<Object> {
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
@Override
|
||||||
|
public Object deserialize(byte[] bytes) throws DeserializationException {
|
||||||
|
|
||||||
|
Assert.notNull(bytes, "JSON byte array cannot be null");
|
||||||
|
|
||||||
|
if (bytes.length == 0) {
|
||||||
|
throw new DeserializationException("Invalid JSON: zero length byte array.");
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
String s = new String(bytes, Strings.UTF_8);
|
||||||
|
return parse(s);
|
||||||
|
} catch (Exception e) {
|
||||||
|
String msg = "Invalid JSON: " + e.getMessage();
|
||||||
|
throw new DeserializationException(msg, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Object parse(String json) throws JSONException {
|
||||||
|
|
||||||
|
JSONTokener tokener = new JSONTokener(json);
|
||||||
|
|
||||||
|
char c = tokener.nextClean(); //peak ahead
|
||||||
|
tokener.back(); //revert
|
||||||
|
|
||||||
|
if (c == '{') { //json object
|
||||||
|
JSONObject o = new JSONObject(tokener);
|
||||||
|
return toMap(o);
|
||||||
|
} else if ( c == '[' ) {
|
||||||
|
JSONArray a = new JSONArray(tokener);
|
||||||
|
return toList(a);
|
||||||
|
} else {
|
||||||
|
//raw json value
|
||||||
|
Object value = tokener.nextValue();
|
||||||
|
return convertIfNecessary(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Map<String, Object> toMap(JSONObject o) {
|
||||||
|
Map<String, Object> map = new LinkedHashMap<>();
|
||||||
|
for (String key : o.keySet()) {
|
||||||
|
Object value = o.get(key);
|
||||||
|
value = convertIfNecessary(value);
|
||||||
|
map.put(key, value);
|
||||||
|
}
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<Object> toList(JSONArray a) {
|
||||||
|
List<Object> list = new ArrayList<>(a.length());
|
||||||
|
for (Object value : a.toList()) {
|
||||||
|
value = convertIfNecessary(value);
|
||||||
|
list.add(value);
|
||||||
|
}
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Object convertIfNecessary(Object v) {
|
||||||
|
Object value = v;
|
||||||
|
if (JSONObject.NULL.equals(value)) {
|
||||||
|
value = null;
|
||||||
|
} else if (value instanceof JSONArray) {
|
||||||
|
value = toList((JSONArray) value);
|
||||||
|
} else if (value instanceof JSONObject) {
|
||||||
|
value = toMap((JSONObject) value);
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,124 @@
|
||||||
|
package io.jsonwebtoken.io.impl.orgjson;
|
||||||
|
|
||||||
|
import io.jsonwebtoken.codec.Encoder;
|
||||||
|
import io.jsonwebtoken.io.SerializationException;
|
||||||
|
import io.jsonwebtoken.io.Serializer;
|
||||||
|
import io.jsonwebtoken.lang.Collections;
|
||||||
|
import io.jsonwebtoken.lang.DateFormats;
|
||||||
|
import io.jsonwebtoken.lang.Objects;
|
||||||
|
import io.jsonwebtoken.lang.Strings;
|
||||||
|
import org.json.JSONArray;
|
||||||
|
import org.json.JSONObject;
|
||||||
|
import org.json.JSONString;
|
||||||
|
import org.json.JSONWriter;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.math.BigInteger;
|
||||||
|
import java.util.Calendar;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
public class OrgJsonSerializer<T> implements Serializer<T> {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public byte[] serialize(T t) throws SerializationException {
|
||||||
|
try {
|
||||||
|
Object o = toJSONInstance(t);
|
||||||
|
return toBytes(o);
|
||||||
|
} catch (SerializationException se) {
|
||||||
|
//propagate
|
||||||
|
throw se;
|
||||||
|
} catch (Exception e) {
|
||||||
|
String msg = "Unable to serialize object of type " + t.getClass().getName() + " to JSON: " + e.getMessage();
|
||||||
|
throw new SerializationException(msg, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Object toJSONInstance(Object object) {
|
||||||
|
|
||||||
|
if (object == null) {
|
||||||
|
return JSONObject.NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (object instanceof JSONObject || object instanceof JSONArray
|
||||||
|
|| JSONObject.NULL.equals(object) || object instanceof JSONString
|
||||||
|
|| object instanceof Byte || object instanceof Character
|
||||||
|
|| object instanceof Short || object instanceof Integer
|
||||||
|
|| object instanceof Long || object instanceof Boolean
|
||||||
|
|| object instanceof Float || object instanceof Double
|
||||||
|
|| object instanceof String || object instanceof BigInteger
|
||||||
|
|| object instanceof BigDecimal || object instanceof Enum) {
|
||||||
|
return object;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (object instanceof Calendar) {
|
||||||
|
object = ((Calendar)object).getTime(); //sets object to date, will be converted in next if-statement:
|
||||||
|
}
|
||||||
|
|
||||||
|
if (object instanceof Date) {
|
||||||
|
Date date = (Date)object;
|
||||||
|
return DateFormats.formatIso8601(date);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (object instanceof byte[]) {
|
||||||
|
return Encoder.BASE64.encode((byte[])object);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (object instanceof char[]) {
|
||||||
|
return new String((char[])object);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (object instanceof Map) {
|
||||||
|
Map<?, ?> map = (Map<?, ?>) object;
|
||||||
|
return toJSONObject(map);
|
||||||
|
}
|
||||||
|
if (object instanceof Collection) {
|
||||||
|
Collection<?> coll = (Collection<?>) object;
|
||||||
|
return toJSONArray(coll);
|
||||||
|
}
|
||||||
|
if (Objects.isArray(object)) {
|
||||||
|
Collection c = Collections.arrayToList(object);
|
||||||
|
return toJSONArray(c);
|
||||||
|
}
|
||||||
|
|
||||||
|
//not an immediately JSON-compatible object and probably a JavaBean (or similar). We can't convert that
|
||||||
|
//directly without using a marshaller of some sort:
|
||||||
|
String msg = "Unable to serialize object of type " + object.getClass().getName() + " to JSON using known heuristics.";
|
||||||
|
throw new SerializationException(msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
private JSONObject toJSONObject(Map<?,?> m) {
|
||||||
|
|
||||||
|
JSONObject obj = new JSONObject();
|
||||||
|
|
||||||
|
for(Map.Entry<?,?> entry : m.entrySet()) {
|
||||||
|
Object k = entry.getKey();
|
||||||
|
Object value = entry.getValue();
|
||||||
|
|
||||||
|
String key = String.valueOf(k);
|
||||||
|
value = toJSONInstance(value);
|
||||||
|
obj.put(key, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
return obj;
|
||||||
|
}
|
||||||
|
|
||||||
|
private JSONArray toJSONArray(Collection c) {
|
||||||
|
|
||||||
|
JSONArray array = new JSONArray();
|
||||||
|
|
||||||
|
for(Object o : c) {
|
||||||
|
o = toJSONInstance(o);
|
||||||
|
array.put(o);
|
||||||
|
}
|
||||||
|
|
||||||
|
return array;
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("WeakerAccess") //for testing
|
||||||
|
protected byte[] toBytes(Object o) {
|
||||||
|
String s = JSONWriter.valueToString(o);
|
||||||
|
return s.getBytes(Strings.UTF_8);
|
||||||
|
}
|
||||||
|
}
|
|
@ -69,7 +69,8 @@ public final class Classes {
|
||||||
* @return the located class
|
* @return the located class
|
||||||
* @throws UnknownClassException if the class cannot be found.
|
* @throws UnknownClassException if the class cannot be found.
|
||||||
*/
|
*/
|
||||||
public static Class forName(String fqcn) throws UnknownClassException {
|
@SuppressWarnings("unchecked")
|
||||||
|
public static <T> Class<T> forName(String fqcn) throws UnknownClassException {
|
||||||
|
|
||||||
Class clazz = THREAD_CL_ACCESSOR.loadClass(fqcn);
|
Class clazz = THREAD_CL_ACCESSOR.loadClass(fqcn);
|
||||||
|
|
||||||
|
@ -132,13 +133,13 @@ public final class Classes {
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
public static Object newInstance(String fqcn) {
|
public static <T> T newInstance(String fqcn) {
|
||||||
return newInstance(forName(fqcn));
|
return (T)newInstance(forName(fqcn));
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
public static Object newInstance(String fqcn, Object... args) {
|
public static <T> T newInstance(String fqcn, Object... args) {
|
||||||
return newInstance(forName(fqcn), args);
|
return (T)newInstance(forName(fqcn), args);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static <T> T newInstance(Class<T> clazz) {
|
public static <T> T newInstance(Class<T> clazz) {
|
||||||
|
|
|
@ -0,0 +1,50 @@
|
||||||
|
package io.jsonwebtoken.lang;
|
||||||
|
|
||||||
|
import java.text.DateFormat;
|
||||||
|
import java.text.ParseException;
|
||||||
|
import java.text.SimpleDateFormat;
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @since 0.10.0
|
||||||
|
*/
|
||||||
|
public class DateFormats {
|
||||||
|
|
||||||
|
private static final String ISO_8601_PATTERN = "yyyy-MM-dd'T'HH:mm:ss'Z'";
|
||||||
|
|
||||||
|
private static final String ISO_8601_MILLIS_PATTERN = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'";
|
||||||
|
|
||||||
|
private static final ThreadLocal<DateFormat> ISO_8601 = new ThreadLocal<DateFormat>() {
|
||||||
|
@Override
|
||||||
|
protected DateFormat initialValue() {
|
||||||
|
return new SimpleDateFormat(ISO_8601_PATTERN);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
private static final ThreadLocal<DateFormat> ISO_8601_MILLIS = new ThreadLocal<DateFormat>() {
|
||||||
|
@Override
|
||||||
|
protected DateFormat initialValue() {
|
||||||
|
return new SimpleDateFormat(ISO_8601_MILLIS_PATTERN);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
public static String formatIso8601(Date date) {
|
||||||
|
return formatIso8601(date, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String formatIso8601(Date date, boolean includeMillis) {
|
||||||
|
if (includeMillis) {
|
||||||
|
return ISO_8601_MILLIS.get().format(date);
|
||||||
|
}
|
||||||
|
return ISO_8601.get().format(date);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Date parseIso8601Date(String s) throws ParseException {
|
||||||
|
Assert.notNull(s, "String argument cannot be null.");
|
||||||
|
if (s.lastIndexOf('.') > -1) { //assume ISO-8601 with milliseconds
|
||||||
|
return ISO_8601_MILLIS.get().parse(s);
|
||||||
|
} else { //assume ISO-8601 without millis:
|
||||||
|
return ISO_8601.get().parse(s);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -24,13 +24,13 @@ import org.junit.Test
|
||||||
import javax.crypto.spec.SecretKeySpec
|
import javax.crypto.spec.SecretKeySpec
|
||||||
import java.security.SecureRandom
|
import java.security.SecureRandom
|
||||||
|
|
||||||
import static org.junit.Assert.*
|
|
||||||
import static ClaimJwtException.INCORRECT_EXPECTED_CLAIM_MESSAGE_TEMPLATE
|
import static ClaimJwtException.INCORRECT_EXPECTED_CLAIM_MESSAGE_TEMPLATE
|
||||||
import static ClaimJwtException.MISSING_EXPECTED_CLAIM_MESSAGE_TEMPLATE
|
import static ClaimJwtException.MISSING_EXPECTED_CLAIM_MESSAGE_TEMPLATE
|
||||||
|
import static org.junit.Assert.*
|
||||||
|
|
||||||
class JwtParserTest {
|
class JwtParserTest {
|
||||||
|
|
||||||
private static final SecureRandom random = new SecureRandom(); //doesn't need to be seeded - just testing
|
private static final SecureRandom random = new SecureRandom() //doesn't need to be seeded - just testing
|
||||||
|
|
||||||
protected static byte[] randomKey() {
|
protected static byte[] randomKey() {
|
||||||
//create random signing key for testing:
|
//create random signing key for testing:
|
||||||
|
@ -826,15 +826,11 @@ class JwtParserTest {
|
||||||
byte[] key = randomKey()
|
byte[] key = randomKey()
|
||||||
|
|
||||||
// not setting expected claim name in JWT
|
// not setting expected claim name in JWT
|
||||||
String compact = Jwts.builder().signWith(SignatureAlgorithm.HS256, key).
|
String compact = Jwts.builder().signWith(SignatureAlgorithm.HS256, key).setIssuer('Dummy').compact()
|
||||||
setIssuer('Dummy').
|
|
||||||
compact()
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// expecting null claim name, but with value
|
// expecting null claim name, but with value
|
||||||
Jwt<Header, Claims> jwt = Jwts.parser().setSigningKey(key).
|
Jwts.parser().setSigningKey(key).require(null, expectedClaimValue).parseClaimsJws(compact)
|
||||||
require(null, expectedClaimValue).
|
|
||||||
parseClaimsJws(compact)
|
|
||||||
fail()
|
fail()
|
||||||
} catch (IllegalArgumentException e) {
|
} catch (IllegalArgumentException e) {
|
||||||
assertEquals(
|
assertEquals(
|
||||||
|
@ -876,9 +872,7 @@ class JwtParserTest {
|
||||||
byte[] key = randomKey()
|
byte[] key = randomKey()
|
||||||
|
|
||||||
// not setting expected claim name in JWT
|
// not setting expected claim name in JWT
|
||||||
String compact = Jwts.builder().signWith(SignatureAlgorithm.HS256, key).
|
String compact = Jwts.builder().signWith(SignatureAlgorithm.HS256, key).setIssuer('Dummy').compact()
|
||||||
setIssuer('Dummy').
|
|
||||||
compact()
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// expecting claim name, but with null value
|
// expecting claim name, but with null value
|
||||||
|
@ -964,6 +958,7 @@ class JwtParserTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testParseRequireIssuedAt_Success() {
|
void testParseRequireIssuedAt_Success() {
|
||||||
|
|
||||||
def issuedAt = new Date(System.currentTimeMillis())
|
def issuedAt = new Date(System.currentTimeMillis())
|
||||||
|
|
||||||
byte[] key = randomKey()
|
byte[] key = randomKey()
|
||||||
|
@ -982,7 +977,7 @@ class JwtParserTest {
|
||||||
assertEquals jwt.getBody().getIssuedAt().getTime(), issuedAtMillis, 0
|
assertEquals jwt.getBody().getIssuedAt().getTime(), issuedAtMillis, 0
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test(expected = IncorrectClaimException)
|
||||||
void testParseRequireIssuedAt_Incorrect_Fail() {
|
void testParseRequireIssuedAt_Incorrect_Fail() {
|
||||||
def goodIssuedAt = new Date(System.currentTimeMillis())
|
def goodIssuedAt = new Date(System.currentTimeMillis())
|
||||||
def badIssuedAt = new Date(System.currentTimeMillis() - 10000)
|
def badIssuedAt = new Date(System.currentTimeMillis() - 10000)
|
||||||
|
@ -993,20 +988,12 @@ class JwtParserTest {
|
||||||
setIssuedAt(badIssuedAt).
|
setIssuedAt(badIssuedAt).
|
||||||
compact()
|
compact()
|
||||||
|
|
||||||
try {
|
|
||||||
Jwts.parser().setSigningKey(key).
|
Jwts.parser().setSigningKey(key).
|
||||||
requireIssuedAt(goodIssuedAt).
|
requireIssuedAt(goodIssuedAt).
|
||||||
parseClaimsJws(compact)
|
parseClaimsJws(compact)
|
||||||
fail()
|
|
||||||
} catch(IncorrectClaimException e) {
|
|
||||||
assertEquals(
|
|
||||||
String.format(INCORRECT_EXPECTED_CLAIM_MESSAGE_TEMPLATE, Claims.ISSUED_AT, goodIssuedAt, badIssuedAt),
|
|
||||||
e.getMessage()
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test(expected = MissingClaimException)
|
||||||
void testParseRequireIssuedAt_Missing_Fail() {
|
void testParseRequireIssuedAt_Missing_Fail() {
|
||||||
def issuedAt = new Date(System.currentTimeMillis() - 10000)
|
def issuedAt = new Date(System.currentTimeMillis() - 10000)
|
||||||
|
|
||||||
|
@ -1016,17 +1003,9 @@ class JwtParserTest {
|
||||||
setSubject("Dummy").
|
setSubject("Dummy").
|
||||||
compact()
|
compact()
|
||||||
|
|
||||||
try {
|
|
||||||
Jwts.parser().setSigningKey(key).
|
Jwts.parser().setSigningKey(key).
|
||||||
requireIssuedAt(issuedAt).
|
requireIssuedAt(issuedAt).
|
||||||
parseClaimsJws(compact)
|
parseClaimsJws(compact)
|
||||||
fail()
|
|
||||||
} catch(MissingClaimException e) {
|
|
||||||
assertEquals(
|
|
||||||
String.format(MISSING_EXPECTED_CLAIM_MESSAGE_TEMPLATE, Claims.ISSUED_AT, issuedAt),
|
|
||||||
e.getMessage()
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -1306,7 +1285,7 @@ class JwtParserTest {
|
||||||
assertEquals jwt.getBody().getExpiration().getTime(), expirationMillis, 0
|
assertEquals jwt.getBody().getExpiration().getTime(), expirationMillis, 0
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test(expected = IncorrectClaimException)
|
||||||
void testParseRequireExpirationAt_Incorrect_Fail() {
|
void testParseRequireExpirationAt_Incorrect_Fail() {
|
||||||
def goodExpiration = new Date(System.currentTimeMillis() + 20000)
|
def goodExpiration = new Date(System.currentTimeMillis() + 20000)
|
||||||
def badExpiration = new Date(System.currentTimeMillis() + 10000)
|
def badExpiration = new Date(System.currentTimeMillis() + 10000)
|
||||||
|
@ -1317,20 +1296,12 @@ class JwtParserTest {
|
||||||
setExpiration(badExpiration).
|
setExpiration(badExpiration).
|
||||||
compact()
|
compact()
|
||||||
|
|
||||||
try {
|
|
||||||
Jwts.parser().setSigningKey(key).
|
Jwts.parser().setSigningKey(key).
|
||||||
requireExpiration(goodExpiration).
|
requireExpiration(goodExpiration).
|
||||||
parseClaimsJws(compact)
|
parseClaimsJws(compact)
|
||||||
fail()
|
|
||||||
} catch(IncorrectClaimException e) {
|
|
||||||
assertEquals(
|
|
||||||
String.format(INCORRECT_EXPECTED_CLAIM_MESSAGE_TEMPLATE, Claims.EXPIRATION, goodExpiration, badExpiration),
|
|
||||||
e.getMessage()
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test(expected = MissingClaimException)
|
||||||
void testParseRequireExpiration_Missing_Fail() {
|
void testParseRequireExpiration_Missing_Fail() {
|
||||||
def expiration = new Date(System.currentTimeMillis() + 10000)
|
def expiration = new Date(System.currentTimeMillis() + 10000)
|
||||||
|
|
||||||
|
@ -1340,17 +1311,9 @@ class JwtParserTest {
|
||||||
setSubject("Dummy").
|
setSubject("Dummy").
|
||||||
compact()
|
compact()
|
||||||
|
|
||||||
try {
|
|
||||||
Jwts.parser().setSigningKey(key).
|
Jwts.parser().setSigningKey(key).
|
||||||
requireExpiration(expiration).
|
requireExpiration(expiration).
|
||||||
parseClaimsJws(compact)
|
parseClaimsJws(compact)
|
||||||
fail()
|
|
||||||
} catch(MissingClaimException e) {
|
|
||||||
assertEquals(
|
|
||||||
String.format(MISSING_EXPECTED_CLAIM_MESSAGE_TEMPLATE, Claims.EXPIRATION, expiration),
|
|
||||||
e.getMessage()
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -1374,7 +1337,7 @@ class JwtParserTest {
|
||||||
assertEquals jwt.getBody().getNotBefore().getTime(), notBeforeMillis, 0
|
assertEquals jwt.getBody().getNotBefore().getTime(), notBeforeMillis, 0
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test(expected = IncorrectClaimException)
|
||||||
void testParseRequireNotBefore_Incorrect_Fail() {
|
void testParseRequireNotBefore_Incorrect_Fail() {
|
||||||
def goodNotBefore = new Date(System.currentTimeMillis() - 20000)
|
def goodNotBefore = new Date(System.currentTimeMillis() - 20000)
|
||||||
def badNotBefore = new Date(System.currentTimeMillis() - 10000)
|
def badNotBefore = new Date(System.currentTimeMillis() - 10000)
|
||||||
|
@ -1385,20 +1348,12 @@ class JwtParserTest {
|
||||||
setNotBefore(badNotBefore).
|
setNotBefore(badNotBefore).
|
||||||
compact()
|
compact()
|
||||||
|
|
||||||
try {
|
|
||||||
Jwts.parser().setSigningKey(key).
|
Jwts.parser().setSigningKey(key).
|
||||||
requireNotBefore(goodNotBefore).
|
requireNotBefore(goodNotBefore).
|
||||||
parseClaimsJws(compact)
|
parseClaimsJws(compact)
|
||||||
fail()
|
|
||||||
} catch(IncorrectClaimException e) {
|
|
||||||
assertEquals(
|
|
||||||
String.format(INCORRECT_EXPECTED_CLAIM_MESSAGE_TEMPLATE, Claims.NOT_BEFORE, goodNotBefore, badNotBefore),
|
|
||||||
e.getMessage()
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test(expected = MissingClaimException)
|
||||||
void testParseRequireNotBefore_Missing_Fail() {
|
void testParseRequireNotBefore_Missing_Fail() {
|
||||||
def notBefore = new Date(System.currentTimeMillis() - 10000)
|
def notBefore = new Date(System.currentTimeMillis() - 10000)
|
||||||
|
|
||||||
|
@ -1408,21 +1363,14 @@ class JwtParserTest {
|
||||||
setSubject("Dummy").
|
setSubject("Dummy").
|
||||||
compact()
|
compact()
|
||||||
|
|
||||||
try {
|
|
||||||
Jwts.parser().setSigningKey(key).
|
Jwts.parser().setSigningKey(key).
|
||||||
requireNotBefore(notBefore).
|
requireNotBefore(notBefore).
|
||||||
parseClaimsJws(compact)
|
parseClaimsJws(compact)
|
||||||
fail()
|
|
||||||
} catch(MissingClaimException e) {
|
|
||||||
assertEquals(
|
|
||||||
String.format(MISSING_EXPECTED_CLAIM_MESSAGE_TEMPLATE, Claims.NOT_BEFORE, notBefore),
|
|
||||||
e.getMessage()
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testParseRequireCustomDate_Success() {
|
void testParseRequireCustomDate_Success() {
|
||||||
|
|
||||||
def aDate = new Date(System.currentTimeMillis())
|
def aDate = new Date(System.currentTimeMillis())
|
||||||
|
|
||||||
byte[] key = randomKey()
|
byte[] key = randomKey()
|
||||||
|
@ -1438,8 +1386,33 @@ class JwtParserTest {
|
||||||
assertEquals jwt.getBody().get("aDate", Date.class), aDate
|
assertEquals jwt.getBody().get("aDate", Date.class), aDate
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test //since 0.10.0
|
||||||
|
void testParseRequireCustomDateWhenClaimIsNotADate() {
|
||||||
|
|
||||||
|
def goodDate = new Date(System.currentTimeMillis())
|
||||||
|
def badDate = 'hello'
|
||||||
|
|
||||||
|
byte[] key = randomKey()
|
||||||
|
|
||||||
|
String compact = Jwts.builder().signWith(SignatureAlgorithm.HS256, key).
|
||||||
|
claim("aDate", badDate).
|
||||||
|
compact()
|
||||||
|
|
||||||
|
try {
|
||||||
|
Jwts.parser().setSigningKey(key).
|
||||||
|
require("aDate", goodDate).
|
||||||
|
parseClaimsJws(compact)
|
||||||
|
fail()
|
||||||
|
} catch(IncorrectClaimException e) {
|
||||||
|
String expected = 'JWT Claim \'aDate\' was expected to be a Date, but its value cannot be converted to a ' +
|
||||||
|
'Date using current heuristics. Value: hello'
|
||||||
|
assertEquals expected, e.getMessage()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testParseRequireCustomDate_Incorrect_Fail() {
|
void testParseRequireCustomDate_Incorrect_Fail() {
|
||||||
|
|
||||||
def goodDate = new Date(System.currentTimeMillis())
|
def goodDate = new Date(System.currentTimeMillis())
|
||||||
def badDate = new Date(System.currentTimeMillis() - 10000)
|
def badDate = new Date(System.currentTimeMillis() - 10000)
|
||||||
|
|
||||||
|
@ -1460,7 +1433,6 @@ class JwtParserTest {
|
||||||
e.getMessage()
|
e.getMessage()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -1534,18 +1506,17 @@ class JwtParserTest {
|
||||||
|
|
||||||
String bad = base64Url(header) + '.' + base64Url(payload) + '.' + base64Url(badSig) + '.' + base64Url(bogus)
|
String bad = base64Url(header) + '.' + base64Url(payload) + '.' + base64Url(badSig) + '.' + base64Url(bogus)
|
||||||
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
Jwts.parser().setSigningKey(randomKey()).parse(bad)
|
Jwts.parser().setSigningKey(randomKey()).parse(bad)
|
||||||
fail()
|
fail()
|
||||||
} catch (MalformedJwtException se) {
|
} catch (MalformedJwtException se) {
|
||||||
assertEquals 'JWT strings must contain exactly 2 period characters. Found: 3', se.message
|
assertEquals 'JWT strings must contain exactly 2 period characters. Found: 3', se.message
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testNoHeaderNoSig() {
|
void testNoHeaderNoSig() {
|
||||||
|
|
||||||
String payload = '{"subject":"Joe"}'
|
String payload = '{"subject":"Joe"}'
|
||||||
|
|
||||||
String jwtStr = '.' + base64Url(payload) + '.'
|
String jwtStr = '.' + base64Url(payload) + '.'
|
||||||
|
@ -1575,16 +1546,17 @@ class JwtParserTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testBadHeaderSig() {
|
void testBadHeaderSig() {
|
||||||
|
|
||||||
String header = '{"alg":"none"}'
|
String header = '{"alg":"none"}'
|
||||||
|
|
||||||
String payload = '{"subject":"Joe"}'
|
String payload = '{"subject":"Joe"}'
|
||||||
|
|
||||||
String sig = ";aklsjdf;kajsd;fkjas;dklfj"
|
String sig = ";aklsjdf;kajsd;fkjas;dklfj"
|
||||||
|
|
||||||
String jwtStr = base64Url(payload) + '.' + base64Url(payload) + '.' + base64Url(sig)
|
String jwtStr = base64Url(header) + '.' + base64Url(payload) + '.' + base64Url(sig)
|
||||||
|
|
||||||
try {
|
try {
|
||||||
Jwt jwt = Jwts.parser().parse(jwtStr)
|
Jwts.parser().parse(jwtStr)
|
||||||
fail()
|
fail()
|
||||||
} catch (MalformedJwtException se) {
|
} catch (MalformedJwtException se) {
|
||||||
assertEquals 'JWT string has a digest/signature, but the header does not reference a valid signature algorithm.', se.message
|
assertEquals 'JWT string has a digest/signature, but the header does not reference a valid signature algorithm.', se.message
|
||||||
|
|
|
@ -15,7 +15,6 @@
|
||||||
*/
|
*/
|
||||||
package io.jsonwebtoken
|
package io.jsonwebtoken
|
||||||
|
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper
|
|
||||||
import io.jsonwebtoken.codec.Encoder
|
import io.jsonwebtoken.codec.Encoder
|
||||||
import io.jsonwebtoken.impl.DefaultHeader
|
import io.jsonwebtoken.impl.DefaultHeader
|
||||||
import io.jsonwebtoken.impl.DefaultJwsHeader
|
import io.jsonwebtoken.impl.DefaultJwsHeader
|
||||||
|
@ -25,6 +24,7 @@ import io.jsonwebtoken.impl.compression.GzipCompressionCodec
|
||||||
import io.jsonwebtoken.impl.crypto.EllipticCurveProvider
|
import io.jsonwebtoken.impl.crypto.EllipticCurveProvider
|
||||||
import io.jsonwebtoken.impl.crypto.MacProvider
|
import io.jsonwebtoken.impl.crypto.MacProvider
|
||||||
import io.jsonwebtoken.impl.crypto.RsaProvider
|
import io.jsonwebtoken.impl.crypto.RsaProvider
|
||||||
|
import io.jsonwebtoken.io.impl.RuntimeClasspathSerializerLocator
|
||||||
import io.jsonwebtoken.lang.Strings
|
import io.jsonwebtoken.lang.Strings
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
|
|
||||||
|
@ -44,6 +44,12 @@ class JwtsTest {
|
||||||
return Encoder.BASE64URL.encode(bytes)
|
return Encoder.BASE64URL.encode(bytes)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected static String toJson(o) {
|
||||||
|
def serializer = new RuntimeClasspathSerializerLocator().getInstance()
|
||||||
|
byte[] bytes = serializer.serialize(o)
|
||||||
|
return new String(bytes, Strings.UTF_8)
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testSubclass() {
|
void testSubclass() {
|
||||||
new Jwts()
|
new Jwts()
|
||||||
|
@ -617,9 +623,8 @@ class JwtsTest {
|
||||||
PublicKey publicKey = kp.getPublic();
|
PublicKey publicKey = kp.getPublic();
|
||||||
PrivateKey privateKey = kp.getPrivate();
|
PrivateKey privateKey = kp.getPrivate();
|
||||||
|
|
||||||
ObjectMapper om = new ObjectMapper()
|
String header = base64Url(toJson(['alg': 'HS256']))
|
||||||
String header = base64Url(om.writeValueAsString(['alg': 'HS256']))
|
String body = base64Url(toJson('foo'))
|
||||||
String body = base64Url(om.writeValueAsString('foo'))
|
|
||||||
String compact = header + '.' + body + '.'
|
String compact = header + '.' + body + '.'
|
||||||
|
|
||||||
// Now for the forgery: simulate an attacker using the RSA public key to sign a token, but
|
// Now for the forgery: simulate an attacker using the RSA public key to sign a token, but
|
||||||
|
@ -650,9 +655,8 @@ class JwtsTest {
|
||||||
PublicKey publicKey = kp.getPublic();
|
PublicKey publicKey = kp.getPublic();
|
||||||
//PrivateKey privateKey = kp.getPrivate();
|
//PrivateKey privateKey = kp.getPrivate();
|
||||||
|
|
||||||
ObjectMapper om = new ObjectMapper()
|
String header = base64Url(toJson(['alg': 'HS256']))
|
||||||
String header = base64Url(om.writeValueAsString(['alg': 'HS256']))
|
String body = base64Url(toJson('foo'))
|
||||||
String body = base64Url(om.writeValueAsString('foo'))
|
|
||||||
String compact = header + '.' + body + '.'
|
String compact = header + '.' + body + '.'
|
||||||
|
|
||||||
// Now for the forgery: simulate an attacker using the RSA public key to sign a token, but
|
// Now for the forgery: simulate an attacker using the RSA public key to sign a token, but
|
||||||
|
@ -683,9 +687,8 @@ class JwtsTest {
|
||||||
PublicKey publicKey = kp.getPublic();
|
PublicKey publicKey = kp.getPublic();
|
||||||
//PrivateKey privateKey = kp.getPrivate();
|
//PrivateKey privateKey = kp.getPrivate();
|
||||||
|
|
||||||
ObjectMapper om = new ObjectMapper()
|
String header = base64Url(toJson(['alg': 'HS256']))
|
||||||
String header = base64Url(om.writeValueAsString(['alg': 'HS256']))
|
String body = base64Url(toJson('foo'))
|
||||||
String body = base64Url(om.writeValueAsString('foo'))
|
|
||||||
String compact = header + '.' + body + '.'
|
String compact = header + '.' + body + '.'
|
||||||
|
|
||||||
// Now for the forgery: simulate an attacker using the Elliptic Curve public key to sign a token, but
|
// Now for the forgery: simulate an attacker using the Elliptic Curve public key to sign a token, but
|
||||||
|
|
|
@ -15,14 +15,14 @@
|
||||||
*/
|
*/
|
||||||
package io.jsonwebtoken.impl
|
package io.jsonwebtoken.impl
|
||||||
|
|
||||||
import com.fasterxml.jackson.core.JsonProcessingException
|
|
||||||
import com.fasterxml.jackson.databind.JsonMappingException
|
|
||||||
import io.jsonwebtoken.Jwts
|
import io.jsonwebtoken.Jwts
|
||||||
import io.jsonwebtoken.SignatureAlgorithm
|
import io.jsonwebtoken.SignatureAlgorithm
|
||||||
import io.jsonwebtoken.codec.Encoder
|
import io.jsonwebtoken.codec.Encoder
|
||||||
import io.jsonwebtoken.codec.EncodingException
|
import io.jsonwebtoken.codec.EncodingException
|
||||||
import io.jsonwebtoken.impl.compression.CompressionCodecs
|
import io.jsonwebtoken.impl.compression.CompressionCodecs
|
||||||
import io.jsonwebtoken.impl.crypto.MacProvider
|
import io.jsonwebtoken.impl.crypto.MacProvider
|
||||||
|
import io.jsonwebtoken.io.SerializationException
|
||||||
|
import io.jsonwebtoken.io.impl.orgjson.OrgJsonSerializer
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
|
|
||||||
import static org.junit.Assert.*
|
import static org.junit.Assert.*
|
||||||
|
@ -181,8 +181,8 @@ class DefaultJwtBuilderTest {
|
||||||
|
|
||||||
def b = new DefaultJwtBuilder() {
|
def b = new DefaultJwtBuilder() {
|
||||||
@Override
|
@Override
|
||||||
protected byte[] toJson(Object o) throws JsonProcessingException {
|
protected byte[] toJson(Object o) throws SerializationException {
|
||||||
throw new JsonMappingException('foo')
|
throw new SerializationException('foo', new Exception())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -192,16 +192,17 @@ class DefaultJwtBuilderTest {
|
||||||
} catch (IllegalStateException ise) {
|
} catch (IllegalStateException ise) {
|
||||||
assertEquals ise.cause.message, 'foo'
|
assertEquals ise.cause.message, 'foo'
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testCompactCompressionCodecJsonProcessingException() {
|
void testCompactCompressionCodecJsonProcessingException() {
|
||||||
def b = new DefaultJwtBuilder() {
|
def b = new DefaultJwtBuilder() {
|
||||||
@Override
|
@Override
|
||||||
protected byte[] toJson(Object o) throws JsonProcessingException {
|
protected byte[] toJson(Object o) throws SerializationException {
|
||||||
if (o instanceof DefaultJwsHeader) { return super.toJson(o) }
|
if (o instanceof DefaultJwsHeader) {
|
||||||
throw new JsonProcessingException('simulate json processing exception on claims')
|
return super.toJson(o)
|
||||||
|
}
|
||||||
|
throw new SerializationException('dummy text', new Exception())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -211,7 +212,7 @@ class DefaultJwtBuilderTest {
|
||||||
b.setClaims(c).compressWith(CompressionCodecs.DEFLATE).compact()
|
b.setClaims(c).compressWith(CompressionCodecs.DEFLATE).compact()
|
||||||
fail()
|
fail()
|
||||||
} catch (IllegalArgumentException iae) {
|
} catch (IllegalArgumentException iae) {
|
||||||
assertEquals iae.message, 'Unable to serialize claims object to json.'
|
assertEquals iae.message, 'Unable to serialize claims object to json: dummy text'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -324,4 +325,24 @@ class DefaultJwtBuilderTest {
|
||||||
assertSame encoder, b.base64UrlEncoder
|
assertSame encoder, b.base64UrlEncoder
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test(expected = IllegalArgumentException)
|
||||||
|
void testSerializeToJsonWithNullArgument() {
|
||||||
|
new DefaultJwtBuilder().serializeToJsonWith(null)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testSerializeToJsonWithCustomSerializer() {
|
||||||
|
def serializer = new OrgJsonSerializer()
|
||||||
|
def b = new DefaultJwtBuilder().serializeToJsonWith(serializer)
|
||||||
|
assertSame serializer, b.serializer
|
||||||
|
|
||||||
|
def key = MacProvider.generateKey(SignatureAlgorithm.HS256)
|
||||||
|
|
||||||
|
String jws = b.signWith(SignatureAlgorithm.HS256, key)
|
||||||
|
.claim('foo', 'bar')
|
||||||
|
.compact()
|
||||||
|
|
||||||
|
assertEquals 'bar', Jwts.parser().setSigningKey(key).parseClaimsJws(jws).getBody().get('foo')
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,9 +1,21 @@
|
||||||
package io.jsonwebtoken.impl
|
package io.jsonwebtoken.impl
|
||||||
|
|
||||||
|
import io.jsonwebtoken.Jwts
|
||||||
|
import io.jsonwebtoken.MalformedJwtException
|
||||||
|
import io.jsonwebtoken.SignatureAlgorithm
|
||||||
import io.jsonwebtoken.codec.Decoder
|
import io.jsonwebtoken.codec.Decoder
|
||||||
import io.jsonwebtoken.codec.DecodingException
|
import io.jsonwebtoken.codec.DecodingException
|
||||||
|
import io.jsonwebtoken.codec.Encoder
|
||||||
|
import io.jsonwebtoken.impl.crypto.MacProvider
|
||||||
|
import io.jsonwebtoken.io.impl.orgjson.OrgJsonDeserializer
|
||||||
|
import io.jsonwebtoken.lang.Strings
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import static org.junit.Assert.*
|
|
||||||
|
import javax.crypto.Mac
|
||||||
|
import javax.crypto.SecretKey
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertEquals
|
||||||
|
import static org.junit.Assert.assertSame
|
||||||
|
|
||||||
// NOTE to the casual reader: even though this test class appears mostly empty, the DefaultJwtParser
|
// NOTE to the casual reader: even though this test class appears mostly empty, the DefaultJwtParser
|
||||||
// implementation is tested to 100% coverage. The vast majority of its tests are in the JwtsTest class. This class
|
// implementation is tested to 100% coverage. The vast majority of its tests are in the JwtsTest class. This class
|
||||||
|
@ -13,11 +25,11 @@ class DefaultJwtParserTest {
|
||||||
|
|
||||||
@Test(expected = IllegalArgumentException)
|
@Test(expected = IllegalArgumentException)
|
||||||
void testBase64UrlDecodeWithNullArgument() {
|
void testBase64UrlDecodeWithNullArgument() {
|
||||||
new DefaultJwtBuilder().base64UrlEncodeWith(null)
|
new DefaultJwtParser().base64UrlDecodeWith(null)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testBase64UrlEncodeWithCustomEncoder() {
|
void testBase64UrlEncodeWithCustomDecoder() {
|
||||||
def decoder = new Decoder() {
|
def decoder = new Decoder() {
|
||||||
@Override
|
@Override
|
||||||
Object decode(Object o) throws DecodingException {
|
Object decode(Object o) throws DecodingException {
|
||||||
|
@ -27,4 +39,76 @@ class DefaultJwtParserTest {
|
||||||
def b = new DefaultJwtParser().base64UrlDecodeWith(decoder)
|
def b = new DefaultJwtParser().base64UrlDecodeWith(decoder)
|
||||||
assertSame decoder, b.base64UrlDecoder
|
assertSame decoder, b.base64UrlDecoder
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test(expected = IllegalArgumentException)
|
||||||
|
void testDeserializeJsonWithNullArgument() {
|
||||||
|
new DefaultJwtParser().deserializeJsonWith(null)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testDesrializeJsonWithCustomSerializer() {
|
||||||
|
def deserializer = new OrgJsonDeserializer()
|
||||||
|
def p = new DefaultJwtParser().deserializeJsonWith(deserializer)
|
||||||
|
assertSame deserializer, p.deserializer
|
||||||
|
|
||||||
|
def key = MacProvider.generateKey(SignatureAlgorithm.HS256)
|
||||||
|
|
||||||
|
String jws = Jwts.builder().claim('foo', 'bar').signWith(SignatureAlgorithm.HS256, key).compact()
|
||||||
|
|
||||||
|
assertEquals 'bar', p.setSigningKey(key).parseClaimsJws(jws).getBody().get('foo')
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(expected = MalformedJwtException)
|
||||||
|
void testParseJwsWithMissingAlg() {
|
||||||
|
|
||||||
|
String header = Encoder.BASE64URL.encode('{"foo":"bar"}'.getBytes(Strings.UTF_8))
|
||||||
|
String body = Encoder.BASE64URL.encode('{"hello":"world"}'.getBytes(Strings.UTF_8))
|
||||||
|
String compact = header + '.' + body + '.'
|
||||||
|
|
||||||
|
SecretKey key = MacProvider.generateKey(SignatureAlgorithm.HS256)
|
||||||
|
Mac mac = Mac.getInstance('HmacSHA256')
|
||||||
|
mac.init(key)
|
||||||
|
byte[] signatureBytes = mac.doFinal(compact.getBytes(Strings.UTF_8))
|
||||||
|
String encodedSignature = Encoder.BASE64URL.encode(signatureBytes)
|
||||||
|
|
||||||
|
String invalidJws = compact + encodedSignature
|
||||||
|
|
||||||
|
new DefaultJwtParser().setSigningKey(key).parseClaimsJws(invalidJws)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(expected = MalformedJwtException)
|
||||||
|
void testParseJwsWithNullAlg() {
|
||||||
|
|
||||||
|
String header = Encoder.BASE64URL.encode('{"alg":null}'.getBytes(Strings.UTF_8))
|
||||||
|
String body = Encoder.BASE64URL.encode('{"hello":"world"}'.getBytes(Strings.UTF_8))
|
||||||
|
String compact = header + '.' + body + '.'
|
||||||
|
|
||||||
|
SecretKey key = MacProvider.generateKey(SignatureAlgorithm.HS256)
|
||||||
|
Mac mac = Mac.getInstance('HmacSHA256')
|
||||||
|
mac.init(key)
|
||||||
|
byte[] signatureBytes = mac.doFinal(compact.getBytes(Strings.UTF_8))
|
||||||
|
String encodedSignature = Encoder.BASE64URL.encode(signatureBytes)
|
||||||
|
|
||||||
|
String invalidJws = compact + encodedSignature
|
||||||
|
|
||||||
|
new DefaultJwtParser().setSigningKey(key).parseClaimsJws(invalidJws)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(expected = MalformedJwtException)
|
||||||
|
void testParseJwsWithEmptyAlg() {
|
||||||
|
|
||||||
|
String header = Encoder.BASE64URL.encode('{"alg":" "}'.getBytes(Strings.UTF_8))
|
||||||
|
String body = Encoder.BASE64URL.encode('{"hello":"world"}'.getBytes(Strings.UTF_8))
|
||||||
|
String compact = header + '.' + body + '.'
|
||||||
|
|
||||||
|
SecretKey key = MacProvider.generateKey(SignatureAlgorithm.HS256)
|
||||||
|
Mac mac = Mac.getInstance('HmacSHA256')
|
||||||
|
mac.init(key)
|
||||||
|
byte[] signatureBytes = mac.doFinal(compact.getBytes(Strings.UTF_8))
|
||||||
|
String encodedSignature = Encoder.BASE64URL.encode(signatureBytes)
|
||||||
|
|
||||||
|
String invalidJws = compact + encodedSignature
|
||||||
|
|
||||||
|
new DefaultJwtParser().setSigningKey(key).parseClaimsJws(invalidJws)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,7 +15,9 @@
|
||||||
*/
|
*/
|
||||||
package io.jsonwebtoken.impl
|
package io.jsonwebtoken.impl
|
||||||
|
|
||||||
|
import io.jsonwebtoken.lang.DateFormats
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
|
|
||||||
import static org.junit.Assert.*
|
import static org.junit.Assert.*
|
||||||
|
|
||||||
class JwtMapTest {
|
class JwtMapTest {
|
||||||
|
@ -28,26 +30,98 @@ class JwtMapTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testToDateFromDate() {
|
void testToDateFromDate() {
|
||||||
|
|
||||||
def d = new Date()
|
def d = new Date()
|
||||||
|
|
||||||
Date date = JwtMap.toDate(d, 'foo')
|
Date date = JwtMap.toDate(d, 'foo')
|
||||||
|
|
||||||
assertSame date, d
|
assertSame date, d
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testToDateFromString() {
|
void testToDateFromCalendar() {
|
||||||
|
def c = Calendar.getInstance(TimeZone.getTimeZone("UTC"))
|
||||||
Date d = new Date(2015, 1, 1, 12, 0, 0)
|
def d = c.getTime()
|
||||||
|
Date date = JwtMap.toDate(c, 'foo')
|
||||||
String s = (d.getTime() / 1000) + '' //JWT timestamps are in seconds - need to strip millis
|
|
||||||
|
|
||||||
Date date = JwtMap.toDate(s, 'foo')
|
|
||||||
|
|
||||||
assertEquals date, d
|
assertEquals date, d
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testToDateFromIso8601String() {
|
||||||
|
Date d = new Date(2015, 1, 1, 12, 0, 0)
|
||||||
|
String s = DateFormats.formatIso8601(d, false)
|
||||||
|
Date date = JwtMap.toDate(s, 'foo')
|
||||||
|
assertEquals date, d
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testToDateFromInvalidIso8601String() {
|
||||||
|
Date d = new Date(2015, 1, 1, 12, 0, 0)
|
||||||
|
String s = d.toString()
|
||||||
|
try {
|
||||||
|
JwtMap.toDate(d.toString(), 'foo')
|
||||||
|
fail()
|
||||||
|
} catch (IllegalArgumentException iae) {
|
||||||
|
assertEquals "'foo' value does not appear to be ISO-8601-formatted: $s" as String, iae.getMessage()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testToDateFromIso8601MillisString() {
|
||||||
|
long millis = System.currentTimeMillis();
|
||||||
|
Date d = new Date(millis)
|
||||||
|
String s = DateFormats.formatIso8601(d)
|
||||||
|
Date date = JwtMap.toDate(s, 'foo')
|
||||||
|
assertEquals date, d
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testToSpecDateWithNull() {
|
||||||
|
assertNull JwtMap.toSpecDate(null, 'exp')
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testToSpecDateWithLong() {
|
||||||
|
long millis = System.currentTimeMillis()
|
||||||
|
long seconds = (millis / 1000l) as long
|
||||||
|
Date d = new Date(seconds * 1000)
|
||||||
|
assertEquals d, JwtMap.toSpecDate(seconds, 'exp')
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testToSpecDateWithString() {
|
||||||
|
Date d = new Date(2015, 1, 1, 12, 0, 0)
|
||||||
|
String s = (d.getTime() / 1000) + '' //JWT timestamps are in seconds - need to strip millis
|
||||||
|
Date date = JwtMap.toSpecDate(s, 'exp')
|
||||||
|
assertEquals date, d
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testToSpecDateWithIso8601String() {
|
||||||
|
long millis = System.currentTimeMillis();
|
||||||
|
Date d = new Date(millis)
|
||||||
|
String s = DateFormats.formatIso8601(d)
|
||||||
|
Date date = JwtMap.toSpecDate(s, 'exp')
|
||||||
|
assertEquals date, d
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testToSpecDateWithDate() {
|
||||||
|
long millis = System.currentTimeMillis();
|
||||||
|
Date d = new Date(millis)
|
||||||
|
Date date = JwtMap.toSpecDate(d, 'exp')
|
||||||
|
assertSame d, date
|
||||||
|
}
|
||||||
|
|
||||||
|
@Deprecated //remove just before 1.0.0
|
||||||
|
@Test
|
||||||
|
void testSetDate() {
|
||||||
|
def m = new JwtMap()
|
||||||
|
m.put('foo', 'bar')
|
||||||
|
m.setDate('foo', null)
|
||||||
|
assertNull m.get('foo')
|
||||||
|
long millis = System.currentTimeMillis()
|
||||||
|
long seconds = (millis / 1000l) as long
|
||||||
|
Date date = new Date(millis)
|
||||||
|
m.setDate('foo', date)
|
||||||
|
assertEquals seconds, m.get('foo')
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -56,7 +130,7 @@ class JwtMapTest {
|
||||||
JwtMap.toDate(new Object() { @Override public String toString() {return 'hi'} }, 'foo')
|
JwtMap.toDate(new Object() { @Override public String toString() {return 'hi'} }, 'foo')
|
||||||
fail()
|
fail()
|
||||||
} catch (IllegalStateException iae) {
|
} catch (IllegalStateException iae) {
|
||||||
assertEquals iae.message, "Cannot convert 'foo' value [hi] to Date instance."
|
assertEquals iae.message, "Cannot create Date from 'foo' value 'hi'."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -126,7 +200,7 @@ class JwtMapTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testEquals() throws Exception {
|
void testEquals() throws Exception {
|
||||||
def m1 = new JwtMap();
|
def m1 = new JwtMap();
|
||||||
m1.put("a", "a");
|
m1.put("a", "a");
|
||||||
|
|
||||||
|
@ -137,7 +211,7 @@ class JwtMapTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testHashcode() throws Exception {
|
void testHashcode() throws Exception {
|
||||||
def m = new JwtMap();
|
def m = new JwtMap();
|
||||||
def hashCodeEmpty = m.hashCode();
|
def hashCodeEmpty = m.hashCode();
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,99 @@
|
||||||
|
package io.jsonwebtoken.io.impl
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper
|
||||||
|
import io.jsonwebtoken.io.Deserializer
|
||||||
|
import io.jsonwebtoken.io.impl.jackson.JacksonDeserializer
|
||||||
|
import io.jsonwebtoken.io.impl.orgjson.OrgJsonDeserializer
|
||||||
|
import org.junit.After
|
||||||
|
import org.junit.Before
|
||||||
|
import org.junit.Test
|
||||||
|
|
||||||
|
import static org.easymock.EasyMock.createMock
|
||||||
|
import static org.junit.Assert.*
|
||||||
|
|
||||||
|
class RuntimeClasspathDeserializerLocatorTest {
|
||||||
|
|
||||||
|
@Before
|
||||||
|
void setUp() {
|
||||||
|
RuntimeClasspathDeserializerLocator.DESERIALIZER.set(null)
|
||||||
|
}
|
||||||
|
|
||||||
|
@After
|
||||||
|
void teardown() {
|
||||||
|
RuntimeClasspathDeserializerLocator.DESERIALIZER.set(null)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testClassIsNotAvailable() {
|
||||||
|
def locator = new RuntimeClasspathDeserializerLocator() {
|
||||||
|
@Override
|
||||||
|
protected boolean isAvailable(String fqcn) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
locator.getInstance()
|
||||||
|
} catch (Exception ex) {
|
||||||
|
assertEquals 'Unable to discover any JSON Deserializer implementations on the classpath.', ex.message
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testCompareAndSetFalse() {
|
||||||
|
Deserializer deserializer = createMock(Deserializer)
|
||||||
|
def locator = new RuntimeClasspathDeserializerLocator() {
|
||||||
|
@Override
|
||||||
|
protected boolean compareAndSet(Deserializer d) {
|
||||||
|
RuntimeClasspathDeserializerLocator.DESERIALIZER.set(deserializer)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def returned = locator.getInstance()
|
||||||
|
assertSame deserializer, returned
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(expected = IllegalStateException)
|
||||||
|
void testLocateReturnsNull() {
|
||||||
|
def locator = new RuntimeClasspathDeserializerLocator() {
|
||||||
|
@Override
|
||||||
|
protected Deserializer locate() {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
locator.getInstance()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(expected = IllegalStateException)
|
||||||
|
void testCompareAndSetFalseWithNullReturn() {
|
||||||
|
def locator = new RuntimeClasspathDeserializerLocator() {
|
||||||
|
@Override
|
||||||
|
protected boolean compareAndSet(Deserializer d) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
locator.getInstance()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testJackson() {
|
||||||
|
def deserializer = new RuntimeClasspathDeserializerLocator().getInstance()
|
||||||
|
assertTrue deserializer instanceof JacksonDeserializer
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testOrgJson() {
|
||||||
|
def locator = new RuntimeClasspathDeserializerLocator() {
|
||||||
|
@Override
|
||||||
|
protected boolean isAvailable(String fqcn) {
|
||||||
|
if (ObjectMapper.class.getName().equals(fqcn)) {
|
||||||
|
return false; //skip it to allow the OrgJson impl to be created
|
||||||
|
}
|
||||||
|
return super.isAvailable(fqcn)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def deserializer = locator.getInstance()
|
||||||
|
assertTrue deserializer instanceof OrgJsonDeserializer
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,99 @@
|
||||||
|
package io.jsonwebtoken.io.impl
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper
|
||||||
|
import io.jsonwebtoken.io.Serializer
|
||||||
|
import io.jsonwebtoken.io.impl.jackson.JacksonSerializer
|
||||||
|
import io.jsonwebtoken.io.impl.orgjson.OrgJsonSerializer
|
||||||
|
import org.junit.After
|
||||||
|
import org.junit.Before
|
||||||
|
import org.junit.Test
|
||||||
|
|
||||||
|
import static org.easymock.EasyMock.createMock
|
||||||
|
import static org.junit.Assert.*
|
||||||
|
|
||||||
|
class RuntimeClasspathSerializerLocatorTest {
|
||||||
|
|
||||||
|
@Before
|
||||||
|
void setUp() {
|
||||||
|
RuntimeClasspathSerializerLocator.SERIALIZER.set(null)
|
||||||
|
}
|
||||||
|
|
||||||
|
@After
|
||||||
|
void teardown() {
|
||||||
|
RuntimeClasspathSerializerLocator.SERIALIZER.set(null)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testClassIsNotAvailable() {
|
||||||
|
def locator = new RuntimeClasspathSerializerLocator() {
|
||||||
|
@Override
|
||||||
|
protected boolean isAvailable(String fqcn) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
locator.getInstance()
|
||||||
|
} catch (Exception ex) {
|
||||||
|
assertEquals 'Unable to discover any JSON Serializer implementations on the classpath.', ex.message
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testCompareAndSetFalse() {
|
||||||
|
Serializer serializer = createMock(Serializer)
|
||||||
|
def locator = new RuntimeClasspathSerializerLocator() {
|
||||||
|
@Override
|
||||||
|
protected boolean compareAndSet(Serializer s) {
|
||||||
|
RuntimeClasspathSerializerLocator.SERIALIZER.set(serializer)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def returned = locator.getInstance()
|
||||||
|
assertSame serializer, returned
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(expected = IllegalStateException)
|
||||||
|
void testLocateReturnsNull() {
|
||||||
|
def locator = new RuntimeClasspathSerializerLocator() {
|
||||||
|
@Override
|
||||||
|
protected Serializer<Object> locate() {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
locator.getInstance()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(expected = IllegalStateException)
|
||||||
|
void testCompareAndSetFalseWithNullReturn() {
|
||||||
|
def locator = new RuntimeClasspathSerializerLocator() {
|
||||||
|
@Override
|
||||||
|
protected boolean compareAndSet(Serializer<Object> s) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
locator.getInstance()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testJackson() {
|
||||||
|
def serializer = new RuntimeClasspathSerializerLocator().getInstance()
|
||||||
|
assertTrue serializer instanceof JacksonSerializer
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testOrgJson() {
|
||||||
|
def locator = new RuntimeClasspathSerializerLocator() {
|
||||||
|
@Override
|
||||||
|
protected boolean isAvailable(String fqcn) {
|
||||||
|
if (ObjectMapper.class.getName().equals(fqcn)) {
|
||||||
|
return false //skip it to allow the OrgJson impl to be created
|
||||||
|
}
|
||||||
|
return super.isAvailable(fqcn)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def serializer = locator.getInstance()
|
||||||
|
assertTrue serializer instanceof OrgJsonSerializer
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,68 @@
|
||||||
|
package io.jsonwebtoken.io.impl.jackson
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper
|
||||||
|
import io.jsonwebtoken.io.DeserializationException
|
||||||
|
import io.jsonwebtoken.lang.Strings
|
||||||
|
import org.junit.Test
|
||||||
|
|
||||||
|
import static org.easymock.EasyMock.createMock
|
||||||
|
import static org.easymock.EasyMock.expect
|
||||||
|
import static org.easymock.EasyMock.replay
|
||||||
|
import static org.easymock.EasyMock.verify
|
||||||
|
import static org.junit.Assert.*
|
||||||
|
|
||||||
|
class JacksonDeserializerTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testDefaultConstructor() {
|
||||||
|
def deserializer = new JacksonDeserializer()
|
||||||
|
assertNotNull deserializer.objectMapper
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testObjectMapperConstructor() {
|
||||||
|
def customOM = new ObjectMapper()
|
||||||
|
def deserializer = new JacksonDeserializer(customOM)
|
||||||
|
assertSame customOM, deserializer.objectMapper
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(expected = IllegalArgumentException)
|
||||||
|
void testObjectMapperConstructorWithNullArgument() {
|
||||||
|
new JacksonDeserializer<>(null)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testDeserialize() {
|
||||||
|
byte[] serialized = '{"hello":"世界"}'.getBytes(Strings.UTF_8)
|
||||||
|
def expected = [hello: '世界']
|
||||||
|
def result = new JacksonDeserializer().deserialize(serialized)
|
||||||
|
assertEquals expected, result
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testDeserializeFailsWithJsonProcessingException() {
|
||||||
|
|
||||||
|
def ex = createMock(IOException)
|
||||||
|
|
||||||
|
expect(ex.getMessage()).andReturn('foo')
|
||||||
|
|
||||||
|
def deserializer = new JacksonDeserializer() {
|
||||||
|
@Override
|
||||||
|
protected Object readValue(byte[] bytes) throws IOException {
|
||||||
|
throw ex
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
replay ex
|
||||||
|
|
||||||
|
try {
|
||||||
|
deserializer.deserialize('{"hello":"世界"}'.getBytes(Strings.UTF_8))
|
||||||
|
fail()
|
||||||
|
} catch (DeserializationException se) {
|
||||||
|
assertEquals 'Unable to deserialize bytes into a java.util.Map instance: foo', se.getMessage()
|
||||||
|
assertSame ex, se.getCause()
|
||||||
|
}
|
||||||
|
|
||||||
|
verify ex
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,107 @@
|
||||||
|
package io.jsonwebtoken.io.impl.jackson
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.core.JsonProcessingException
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper
|
||||||
|
import io.jsonwebtoken.codec.Encoder
|
||||||
|
import io.jsonwebtoken.io.SerializationException
|
||||||
|
import io.jsonwebtoken.lang.Strings
|
||||||
|
import org.junit.Test
|
||||||
|
|
||||||
|
import static org.easymock.EasyMock.createMock
|
||||||
|
import static org.easymock.EasyMock.expect
|
||||||
|
import static org.easymock.EasyMock.replay
|
||||||
|
import static org.easymock.EasyMock.verify
|
||||||
|
import static org.junit.Assert.*
|
||||||
|
|
||||||
|
class JacksonSerializerTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testDefaultConstructor() {
|
||||||
|
def serializer = new JacksonSerializer()
|
||||||
|
assertNotNull serializer.objectMapper
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testObjectMapperConstructor() {
|
||||||
|
def customOM = new ObjectMapper()
|
||||||
|
def serializer = new JacksonSerializer<>(customOM)
|
||||||
|
assertSame customOM, serializer.objectMapper
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(expected = IllegalArgumentException)
|
||||||
|
void testObjectMapperConstructorWithNullArgument() {
|
||||||
|
new JacksonSerializer<>(null)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testByte() {
|
||||||
|
byte[] expected = "120".getBytes(Strings.UTF_8) //ascii("x") = 120
|
||||||
|
byte[] bytes = "x".getBytes(Strings.UTF_8)
|
||||||
|
byte[] result = new JacksonSerializer().serialize(bytes[0]) //single byte
|
||||||
|
assertTrue Arrays.equals(expected, result)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testByteArray() { //expect Base64 string by default:
|
||||||
|
byte[] bytes = "hi".getBytes(Strings.UTF_8)
|
||||||
|
String encoded = Encoder.BASE64.encode(bytes)
|
||||||
|
String expected = "\"$encoded\"" as String
|
||||||
|
byte[] result = new JacksonSerializer().serialize(bytes)
|
||||||
|
assertEquals expected, new String(result, Strings.UTF_8)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testEmptyByteArray() { //expect Base64 string by default:
|
||||||
|
byte[] bytes = new byte[0]
|
||||||
|
String encoded = Encoder.BASE64.encode(bytes)
|
||||||
|
String expected = "\"$encoded\"" as String
|
||||||
|
byte[] result = new JacksonSerializer().serialize(bytes)
|
||||||
|
assertEquals expected, new String(result, Strings.UTF_8)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testChar() { //expect Base64 string by default:
|
||||||
|
byte[] result = new JacksonSerializer().serialize('h' as char)
|
||||||
|
assertEquals "\"h\"", new String(result, Strings.UTF_8)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testCharArray() { //expect Base64 string by default:
|
||||||
|
byte[] result = new JacksonSerializer().serialize("hi".toCharArray())
|
||||||
|
assertEquals "\"hi\"", new String(result, Strings.UTF_8)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testSerialize() {
|
||||||
|
byte[] expected = '{"hello":"世界"}'.getBytes(Strings.UTF_8)
|
||||||
|
byte[] result = new JacksonSerializer().serialize([hello: '世界'])
|
||||||
|
assertTrue Arrays.equals(expected, result)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testSerializeFailsWithJsonProcessingException() {
|
||||||
|
|
||||||
|
def ex = createMock(JsonProcessingException)
|
||||||
|
|
||||||
|
expect(ex.getMessage()).andReturn('foo')
|
||||||
|
|
||||||
|
def serializer = new JacksonSerializer() {
|
||||||
|
@Override
|
||||||
|
protected byte[] writeValueAsBytes(Object o) throws JsonProcessingException {
|
||||||
|
throw ex
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
replay ex
|
||||||
|
|
||||||
|
try {
|
||||||
|
serializer.serialize([hello: 'world'])
|
||||||
|
fail()
|
||||||
|
} catch (SerializationException se) {
|
||||||
|
assertEquals 'Unable to serialize object: foo', se.getMessage()
|
||||||
|
assertSame ex, se.getCause()
|
||||||
|
}
|
||||||
|
|
||||||
|
verify ex
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,147 @@
|
||||||
|
package io.jsonwebtoken.io.impl.orgjson
|
||||||
|
|
||||||
|
import io.jsonwebtoken.io.DeserializationException
|
||||||
|
import io.jsonwebtoken.lang.Strings
|
||||||
|
import org.junit.Test
|
||||||
|
import static org.junit.Assert.*
|
||||||
|
|
||||||
|
class OrgJsonDeserializerTest {
|
||||||
|
|
||||||
|
@Test(expected=IllegalArgumentException)
|
||||||
|
void testNullArgument() {
|
||||||
|
def d = new OrgJsonDeserializer()
|
||||||
|
d.deserialize(null)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(expected = DeserializationException)
|
||||||
|
void testEmptyByteArray() {
|
||||||
|
def d = new OrgJsonDeserializer()
|
||||||
|
d.deserialize(new byte[0])
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(expected = DeserializationException)
|
||||||
|
void testInvalidJson() {
|
||||||
|
def d = new OrgJsonDeserializer()
|
||||||
|
d.deserialize('"'.getBytes(Strings.UTF_8))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testLiteralNull() {
|
||||||
|
def d = new OrgJsonDeserializer();
|
||||||
|
def b = 'null'.getBytes(Strings.UTF_8)
|
||||||
|
def value = d.deserialize(b)
|
||||||
|
assertNull value
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testLiteralTrue() {
|
||||||
|
def d = new OrgJsonDeserializer();
|
||||||
|
def b = 'true'.getBytes(Strings.UTF_8)
|
||||||
|
def value = d.deserialize(b)
|
||||||
|
assertEquals Boolean.TRUE, value
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testLiteralFalse() {
|
||||||
|
def d = new OrgJsonDeserializer();
|
||||||
|
def b = 'false'.getBytes(Strings.UTF_8)
|
||||||
|
def value = d.deserialize(b)
|
||||||
|
assertEquals Boolean.FALSE, value
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testLiteralInteger() {
|
||||||
|
def d = new OrgJsonDeserializer();
|
||||||
|
def b = '1'.getBytes(Strings.UTF_8)
|
||||||
|
def value = d.deserialize(b)
|
||||||
|
assert value instanceof Integer
|
||||||
|
assertEquals 1 as Integer, value
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testLiteralDecimal() {
|
||||||
|
def d = new OrgJsonDeserializer();
|
||||||
|
def b = '3.14159'.getBytes(Strings.UTF_8)
|
||||||
|
def value = d.deserialize(b)
|
||||||
|
assert value instanceof Double
|
||||||
|
assertEquals 3.14159 as Double, value, 0d
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testEmptyArray() {
|
||||||
|
def d = new OrgJsonDeserializer();
|
||||||
|
def b = '[]'.getBytes(Strings.UTF_8)
|
||||||
|
def value = d.deserialize(b)
|
||||||
|
assert value instanceof List
|
||||||
|
assertEquals 0, value.size()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testSimpleArray() {
|
||||||
|
def d = new OrgJsonDeserializer();
|
||||||
|
def b = '[1, 2]'.getBytes(Strings.UTF_8)
|
||||||
|
def value = d.deserialize(b)
|
||||||
|
assert value instanceof List
|
||||||
|
def expected = [1, 2]
|
||||||
|
assertEquals expected, value
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testArrayWithNullElements() {
|
||||||
|
def d = new OrgJsonDeserializer();
|
||||||
|
def b = '[1, null, 3]'.getBytes(Strings.UTF_8)
|
||||||
|
def value = d.deserialize(b)
|
||||||
|
assert value instanceof List
|
||||||
|
def expected = [1, null, 3]
|
||||||
|
assertEquals expected, value
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testEmptyObject() {
|
||||||
|
def d = new OrgJsonDeserializer();
|
||||||
|
def b = '{}'.getBytes(Strings.UTF_8)
|
||||||
|
def value = d.deserialize(b)
|
||||||
|
assert value instanceof Map
|
||||||
|
assertEquals 0, value.size()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testSimpleObject() {
|
||||||
|
def d = new OrgJsonDeserializer();
|
||||||
|
def b = '{"hello": "世界"}'.getBytes(Strings.UTF_8)
|
||||||
|
def value = d.deserialize(b)
|
||||||
|
assert value instanceof Map
|
||||||
|
def expected = [hello: '世界']
|
||||||
|
assertEquals expected, value
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testObjectWithKeyHavingNullValue() {
|
||||||
|
def d = new OrgJsonDeserializer();
|
||||||
|
def b = '{"hello": "世界", "test": null}'.getBytes(Strings.UTF_8)
|
||||||
|
def value = d.deserialize(b)
|
||||||
|
assert value instanceof Map
|
||||||
|
def expected = [hello: '世界', test: null]
|
||||||
|
assertEquals expected, value
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testObjectWithKeyHavingArrayValue() {
|
||||||
|
def d = new OrgJsonDeserializer();
|
||||||
|
def b = '{"hello": "世界", "test": [1, 2]}'.getBytes(Strings.UTF_8)
|
||||||
|
def value = d.deserialize(b)
|
||||||
|
assert value instanceof Map
|
||||||
|
def expected = [hello: '世界', test: [1, 2]]
|
||||||
|
assertEquals expected, value
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testObjectWithKeyHavingObjectValue() {
|
||||||
|
def d = new OrgJsonDeserializer();
|
||||||
|
def b = '{"hello": "世界", "test": {"foo": "bar"}}'.getBytes(Strings.UTF_8)
|
||||||
|
def value = d.deserialize(b)
|
||||||
|
assert value instanceof Map
|
||||||
|
def expected = [hello: '世界', test: [foo: 'bar']]
|
||||||
|
assertEquals expected, value
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,257 @@
|
||||||
|
package io.jsonwebtoken.io.impl.orgjson
|
||||||
|
|
||||||
|
import io.jsonwebtoken.SignatureAlgorithm
|
||||||
|
import io.jsonwebtoken.codec.Encoder
|
||||||
|
import io.jsonwebtoken.io.SerializationException
|
||||||
|
import io.jsonwebtoken.lang.DateFormats
|
||||||
|
import io.jsonwebtoken.lang.Strings
|
||||||
|
import org.json.JSONObject
|
||||||
|
import org.json.JSONString
|
||||||
|
import org.junit.Before
|
||||||
|
import org.junit.Test
|
||||||
|
import static org.junit.Assert.*
|
||||||
|
|
||||||
|
class OrgJsonSerializerTest {
|
||||||
|
|
||||||
|
private OrgJsonSerializer s
|
||||||
|
|
||||||
|
@Before
|
||||||
|
void setUp() {
|
||||||
|
s = new OrgJsonSerializer()
|
||||||
|
}
|
||||||
|
|
||||||
|
private String ser(Object o) {
|
||||||
|
byte[] bytes = s.serialize(o)
|
||||||
|
return new String(bytes, Strings.UTF_8)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(expected = SerializationException)
|
||||||
|
void testInvalidArgument() {
|
||||||
|
s.serialize(new Object())
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testToBytesFailure() {
|
||||||
|
|
||||||
|
final IllegalArgumentException iae = new IllegalArgumentException("foo")
|
||||||
|
|
||||||
|
s = new OrgJsonSerializer() {
|
||||||
|
@Override
|
||||||
|
protected byte[] toBytes(Object o) {
|
||||||
|
throw iae
|
||||||
|
}
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
s.serialize("hello")
|
||||||
|
fail()
|
||||||
|
} catch (SerializationException se) {
|
||||||
|
assertTrue se.getMessage().endsWith(iae.getMessage())
|
||||||
|
assertSame iae, se.getCause()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testNull() {
|
||||||
|
assertEquals 'null', ser(null)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testJSONObjectNull() {
|
||||||
|
assertEquals 'null', ser(JSONObject.NULL)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testJSONString() {
|
||||||
|
def jsonString = new JSONString() {
|
||||||
|
@Override
|
||||||
|
String toJSONString() {
|
||||||
|
return '"foo"'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assertEquals '"foo"', ser(jsonString)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testTrue() {
|
||||||
|
assertEquals 'true', ser(Boolean.TRUE)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testFalse() {
|
||||||
|
assertEquals 'false', ser(Boolean.FALSE)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testByte() {
|
||||||
|
assertEquals '120', ser("x".getBytes(Strings.UTF_8)[0]) //ascii("x") == 120
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testByteArray() { //expect Base64 string by default:
|
||||||
|
byte[] bytes = "hi".getBytes(Strings.UTF_8)
|
||||||
|
String encoded = Encoder.BASE64.encode(bytes)
|
||||||
|
String expected = "\"$encoded\"" as String
|
||||||
|
assertEquals expected, ser(bytes)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testEmptyByteArray() { //base64 --> zero bytes == zero-length string:
|
||||||
|
assertEquals "\"\"", ser(new byte[0])
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testChar() {
|
||||||
|
assertEquals "\"h\"", ser('h' as char)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testCharArray() {
|
||||||
|
assertEquals "\"hi\"", ser("hi".toCharArray())
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testEmptyCharArray() { //no chars == empty string:
|
||||||
|
assertEquals "\"\"", ser(new char[0])
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testShort() {
|
||||||
|
assertEquals '8', ser(8 as short)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testInteger() {
|
||||||
|
assertEquals '1', ser(1 as Integer)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testLong() {
|
||||||
|
assertEquals '42', ser(42 as Long)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testBigInteger() {
|
||||||
|
assertEquals '42', ser(BigInteger.valueOf(42 as Long))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testFloat() {
|
||||||
|
assertEquals '3.14159', ser(3.14159 as Float)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testDouble() {
|
||||||
|
assertEquals '3.14159', ser(3.14159 as Double)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testBigDecimal() {
|
||||||
|
assertEquals '3.14159', ser(BigDecimal.valueOf(3.14159 as Double))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testEnum() {
|
||||||
|
assertEquals '"HS256"', ser(SignatureAlgorithm.HS256)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testEmptyString() {
|
||||||
|
assertEquals '""', ser('')
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testWhitespaceString() {
|
||||||
|
String value = " \n\r\t "
|
||||||
|
assertEquals '" \\n\\r\\t "' as String, ser(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testSimpleString() {
|
||||||
|
assertEquals '"hello 世界"', ser('hello 世界')
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testDate() {
|
||||||
|
Date now = new Date()
|
||||||
|
String formatted = DateFormats.formatIso8601(now)
|
||||||
|
assertEquals "\"$formatted\"" as String, ser(now)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testCalendar() {
|
||||||
|
def cal = Calendar.getInstance(TimeZone.getTimeZone("UTC"))
|
||||||
|
def now = cal.getTime()
|
||||||
|
String formatted = DateFormats.formatIso8601(now)
|
||||||
|
assertEquals "\"$formatted\"" as String, ser(cal)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testSimpleIntArray() {
|
||||||
|
assertEquals '[1,2]', ser( [1, 2] as int[] )
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testIntegerArrayWithNullElements() {
|
||||||
|
assertEquals '[1,null]', ser( [1, null] as Integer[] )
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testIntegerList() {
|
||||||
|
assertEquals '[1,2]', ser( [1, 2] as List)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testEmptyObject() {
|
||||||
|
assertEquals '{}', ser([:])
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testSimpleObject() {
|
||||||
|
assertEquals '{"hello":"世界"}', ser([hello: '世界'])
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testObjectWithKeyHavingNullValue() {
|
||||||
|
//depending on the test platform, and that JSON doesn't require members to be ordered, either of the
|
||||||
|
//two strings are fine (they're the same data, just the member order is different):
|
||||||
|
String acceptable1 = '{"hello":"世界","test":null}'
|
||||||
|
String acceptable2 = '{"test":null,"hello":"世界"}'
|
||||||
|
String result = ser([test: null, hello: '世界'])
|
||||||
|
assertTrue acceptable1.equals(result) || acceptable2.equals(result)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testObjectWithKeyHavingArrayValue() {
|
||||||
|
//depending on the test platform, and that JSON doesn't require members to be ordered, either of the
|
||||||
|
//two strings are fine (they're the same data, just the member order is different):
|
||||||
|
String acceptable1 = '{"test":[1,2],"hello":"世界"}'
|
||||||
|
String acceptable2 = '{"hello":"世界","test":[1,2]}'
|
||||||
|
String result = ser([test: [1, 2], hello: '世界'])
|
||||||
|
assertTrue acceptable1.equals(result) || acceptable2.equals(result)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testObjectWithKeyHavingObjectValue() {
|
||||||
|
//depending on the test platform, and that JSON doesn't require members to be ordered, either of the
|
||||||
|
//two strings are fine (they're the same data, just the member order is different):
|
||||||
|
String acceptable1 = '{"test":{"foo":"bar"},"hello":"世界"}'
|
||||||
|
String acceptable2 = '{"hello":"世界","test":{"foo":"bar"}}'
|
||||||
|
String result = ser([test: [foo: 'bar'], hello: '世界'])
|
||||||
|
assertTrue acceptable1.equals(result) || acceptable2.equals(result)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testListWithNullElements() {
|
||||||
|
assertEquals '[1,null,null]', ser( [1, null, null] as List)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testListWithSingleNullElement() {
|
||||||
|
assertEquals '[null]', ser([null] as List)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testListWithNestedObject() {
|
||||||
|
assertEquals '[1,null,{"hello":"世界"}]', ser([1, null, [hello: '世界']])
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue