mirror of https://github.com/jwtk/jjwt.git
335: initial pluggable JSON (de)serialization support with Jackson and org.json as the first implementations, with Jackson being the default. Added tests to retain 100% code coverage.
This commit is contained in:
parent
bae78f03f4
commit
8afca0d0df
27
pom.xml
27
pom.xml
|
@ -86,6 +86,7 @@
|
|||
<buildNumber>${user.name}-${maven.build.timestamp}</buildNumber>
|
||||
|
||||
<jackson.version>2.9.6</jackson.version>
|
||||
<orgjson.version>20180130</orgjson.version>
|
||||
|
||||
<!-- Optional Runtime Dependencies: -->
|
||||
<bouncycastle.version>1.56</bouncycastle.version>
|
||||
|
@ -98,18 +99,27 @@
|
|||
<powermock.version>2.0.0-beta.5</powermock.version> <!-- necessary for Java 9 support -->
|
||||
<failsafe.plugin.version>2.22.0</failsafe.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>
|
||||
|
||||
<dependencies>
|
||||
|
||||
<!-- Optional Dependencies: -->
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.core</groupId>
|
||||
<artifactId>jackson-databind</artifactId>
|
||||
<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>
|
||||
|
||||
<!-- Optional Dependencies: -->
|
||||
<dependency>
|
||||
<groupId>org.bouncycastle</groupId>
|
||||
<artifactId>bcprov-jdk15on</artifactId>
|
||||
|
@ -295,14 +305,13 @@
|
|||
<version>${clover.version}</version>
|
||||
<configuration>
|
||||
<excludes>
|
||||
<exclude>**/*Test*</exclude>
|
||||
<!-- leaving out lang as it mostly comes from other sources -->
|
||||
<exclude>io/jsonwebtoken/lang/*</exclude>
|
||||
</excludes>
|
||||
<methodPercentage>100%</methodPercentage>
|
||||
<statementPercentage>100%</statementPercentage>
|
||||
<conditionalPercentage>100%</conditionalPercentage>
|
||||
<targetPercentage>100%</targetPercentage>
|
||||
<methodPercentage>100.000000%</methodPercentage>
|
||||
<statementPercentage>100.000000%</statementPercentage>
|
||||
<conditionalPercentage>100.000000%</conditionalPercentage>
|
||||
<targetPercentage>100.000000%</targetPercentage>
|
||||
</configuration>
|
||||
<executions>
|
||||
<execution>
|
||||
|
@ -310,8 +319,8 @@
|
|||
<phase>test</phase>
|
||||
<goals>
|
||||
<goal>instrument</goal>
|
||||
<goal>check</goal>
|
||||
<goal>clover</goal>
|
||||
<goal>check</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
package io.jsonwebtoken;
|
||||
|
||||
import io.jsonwebtoken.codec.Encoder;
|
||||
import io.jsonwebtoken.io.Serializer;
|
||||
|
||||
import java.security.Key;
|
||||
import java.util.Date;
|
||||
|
@ -426,6 +427,20 @@ public interface JwtBuilder extends ClaimsMutator<JwtBuilder> {
|
|||
*/
|
||||
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
|
||||
* <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.impl.DefaultClock;
|
||||
import io.jsonwebtoken.io.Deserializer;
|
||||
|
||||
import java.security.Key;
|
||||
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.
|
||||
|
@ -198,7 +200,7 @@ public interface JwtParser {
|
|||
* {@code byte[]} variant will be removed before the 1.0.0 release.</p>
|
||||
*
|
||||
* @param base64EncodedSecretKey the BASE64-encoded algorithm-specific signature verification key to use to validate
|
||||
* any discovered JWS digital signature.
|
||||
* any discovered JWS digital signature.
|
||||
* @return the parser for method chaining.
|
||||
*/
|
||||
JwtParser setSigningKey(String base64EncodedSecretKey);
|
||||
|
@ -282,6 +284,22 @@ public interface JwtParser {
|
|||
*/
|
||||
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}
|
||||
* otherwise.
|
||||
|
@ -369,7 +387,7 @@ public interface JwtParser {
|
|||
* @since 0.2
|
||||
*/
|
||||
<T> T parse(String jwt, JwtHandler<T> handler)
|
||||
throws ExpiredJwtException, UnsupportedJwtException, MalformedJwtException, SignatureException, IllegalArgumentException;
|
||||
throws ExpiredJwtException, UnsupportedJwtException, MalformedJwtException, SignatureException, IllegalArgumentException;
|
||||
|
||||
/**
|
||||
* Parses the specified compact serialized JWT string based on the builder's current configuration state and
|
||||
|
@ -399,7 +417,7 @@ public interface JwtParser {
|
|||
* @since 0.2
|
||||
*/
|
||||
Jwt<Header, String> parsePlaintextJwt(String plaintextJwt)
|
||||
throws UnsupportedJwtException, MalformedJwtException, SignatureException, IllegalArgumentException;
|
||||
throws UnsupportedJwtException, MalformedJwtException, SignatureException, IllegalArgumentException;
|
||||
|
||||
/**
|
||||
* Parses the specified compact serialized JWT string based on the builder's current configuration state and
|
||||
|
@ -430,7 +448,7 @@ public interface JwtParser {
|
|||
* @since 0.2
|
||||
*/
|
||||
Jwt<Header, Claims> parseClaimsJwt(String claimsJwt)
|
||||
throws ExpiredJwtException, UnsupportedJwtException, MalformedJwtException, SignatureException, IllegalArgumentException;
|
||||
throws ExpiredJwtException, UnsupportedJwtException, MalformedJwtException, SignatureException, IllegalArgumentException;
|
||||
|
||||
/**
|
||||
* Parses the specified compact serialized JWS string based on the builder's current configuration state and
|
||||
|
@ -458,7 +476,7 @@ public interface JwtParser {
|
|||
* @since 0.2
|
||||
*/
|
||||
Jws<String> parsePlaintextJws(String plaintextJws)
|
||||
throws UnsupportedJwtException, MalformedJwtException, SignatureException, IllegalArgumentException;
|
||||
throws UnsupportedJwtException, MalformedJwtException, SignatureException, IllegalArgumentException;
|
||||
|
||||
/**
|
||||
* Parses the specified compact serialized JWS string based on the builder's current configuration state and
|
||||
|
@ -487,5 +505,5 @@ public interface JwtParser {
|
|||
* @since 0.2
|
||||
*/
|
||||
Jws<Claims> parseClaimsJws(String claimsJws)
|
||||
throws ExpiredJwtException, UnsupportedJwtException, MalformedJwtException, SignatureException, IllegalArgumentException;
|
||||
throws ExpiredJwtException, UnsupportedJwtException, MalformedJwtException, SignatureException, IllegalArgumentException;
|
||||
}
|
||||
|
|
|
@ -71,7 +71,7 @@ public class DefaultClaims extends JwtMap implements Claims {
|
|||
|
||||
@Override
|
||||
public Claims setExpiration(Date exp) {
|
||||
setDate(Claims.EXPIRATION, exp);
|
||||
setDateAsSeconds(Claims.EXPIRATION, exp);
|
||||
return this;
|
||||
}
|
||||
|
||||
|
@ -82,7 +82,7 @@ public class DefaultClaims extends JwtMap implements Claims {
|
|||
|
||||
@Override
|
||||
public Claims setNotBefore(Date nbf) {
|
||||
setDate(Claims.NOT_BEFORE, nbf);
|
||||
setDateAsSeconds(Claims.NOT_BEFORE, nbf);
|
||||
return this;
|
||||
}
|
||||
|
||||
|
@ -93,7 +93,7 @@ public class DefaultClaims extends JwtMap implements Claims {
|
|||
|
||||
@Override
|
||||
public Claims setIssuedAt(Date iat) {
|
||||
setDate(Claims.ISSUED_AT, iat);
|
||||
setDateAsSeconds(Claims.ISSUED_AT, iat);
|
||||
return this;
|
||||
}
|
||||
|
||||
|
@ -108,25 +108,35 @@ public class DefaultClaims extends JwtMap implements Claims {
|
|||
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
|
||||
public <T> T get(String claimName, Class<T> requiredType) {
|
||||
Object value = get(claimName);
|
||||
if (value == null) { return null; }
|
||||
|
||||
if (Claims.EXPIRATION.equals(claimName) ||
|
||||
Claims.ISSUED_AT.equals(claimName) ||
|
||||
Claims.NOT_BEFORE.equals(claimName)
|
||||
) {
|
||||
value = getDate(claimName);
|
||||
Object value = get(claimName);
|
||||
if (value == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (Date.class.equals(requiredType)) {
|
||||
if (isSpecDate(claimName)) {
|
||||
value = toSpecDate(value, claimName);
|
||||
} else {
|
||||
value = toDate(value, claimName);
|
||||
}
|
||||
}
|
||||
|
||||
return castClaimValue(value, 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) {
|
||||
int intValue = (Integer) value;
|
||||
|
|
|
@ -15,8 +15,6 @@
|
|||
*/
|
||||
package io.jsonwebtoken.impl;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import io.jsonwebtoken.Claims;
|
||||
import io.jsonwebtoken.CompressionCodec;
|
||||
import io.jsonwebtoken.Header;
|
||||
|
@ -29,7 +27,11 @@ import io.jsonwebtoken.codec.Decoder;
|
|||
import io.jsonwebtoken.codec.Encoder;
|
||||
import io.jsonwebtoken.impl.crypto.DefaultJwtSigner;
|
||||
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.Classes;
|
||||
import io.jsonwebtoken.lang.Collections;
|
||||
import io.jsonwebtoken.lang.Strings;
|
||||
|
||||
|
@ -40,8 +42,6 @@ import java.util.Map;
|
|||
|
||||
public class DefaultJwtBuilder implements JwtBuilder {
|
||||
|
||||
private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
|
||||
|
||||
private Header header;
|
||||
private Claims claims;
|
||||
private String payload;
|
||||
|
@ -49,10 +49,19 @@ public class DefaultJwtBuilder implements JwtBuilder {
|
|||
private SignatureAlgorithm algorithm;
|
||||
private Key key;
|
||||
|
||||
private Serializer<Map<String,?>> serializer;
|
||||
|
||||
private Encoder<byte[], String> base64UrlEncoder = Encoder.BASE64URL;
|
||||
|
||||
private CompressionCodec compressionCodec;
|
||||
|
||||
@Override
|
||||
public JwtBuilder serializeToJsonWith(Serializer<Map<String,?>> serializer) {
|
||||
Assert.notNull(serializer, "Serializer cannot be null.");
|
||||
this.serializer = serializer;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public JwtBuilder base64UrlEncodeWith(Encoder<byte[], String> base64UrlEncoder) {
|
||||
Assert.notNull(base64UrlEncoder, "base64UrlEncoder cannot be null.");
|
||||
|
@ -270,6 +279,14 @@ public class DefaultJwtBuilder implements JwtBuilder {
|
|||
|
||||
@Override
|
||||
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)) {
|
||||
throw new IllegalStateException("Either 'payload' or 'claims' must be specified.");
|
||||
}
|
||||
|
@ -304,8 +321,8 @@ public class DefaultJwtBuilder implements JwtBuilder {
|
|||
byte[] bytes;
|
||||
try {
|
||||
bytes = this.payload != null ? payload.getBytes(Strings.UTF_8) : toJson(claims);
|
||||
} catch (JsonProcessingException e) {
|
||||
throw new IllegalArgumentException("Unable to serialize claims object to json.");
|
||||
} catch (SerializationException e) {
|
||||
throw new IllegalArgumentException("Unable to serialize claims object to json: " + e.getMessage(), e);
|
||||
}
|
||||
|
||||
if (compressionCodec != null) {
|
||||
|
@ -339,18 +356,25 @@ public class DefaultJwtBuilder implements JwtBuilder {
|
|||
return new DefaultJwtSigner(alg, key, base64UrlEncoder);
|
||||
}
|
||||
|
||||
@Deprecated // remove before 1.0 - call the serializer and base64UrlEncoder directly
|
||||
protected String base64UrlEncode(Object o, String errMsg) {
|
||||
Assert.isInstanceOf(Map.class, o, "object argument must be a map.");
|
||||
Map m = (Map)o;
|
||||
byte[] bytes;
|
||||
try {
|
||||
bytes = toJson(o);
|
||||
} catch (JsonProcessingException e) {
|
||||
bytes = toJson(m);
|
||||
} catch (SerializationException e) {
|
||||
throw new IllegalStateException(errMsg, e);
|
||||
}
|
||||
|
||||
return base64UrlEncoder.encode(bytes);
|
||||
}
|
||||
|
||||
protected byte[] toJson(Object object) throws JsonProcessingException {
|
||||
return OBJECT_MAPPER.writeValueAsBytes(object);
|
||||
@SuppressWarnings("unchecked")
|
||||
@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;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import io.jsonwebtoken.ClaimJwtException;
|
||||
import io.jsonwebtoken.Claims;
|
||||
import io.jsonwebtoken.Clock;
|
||||
|
@ -42,26 +41,25 @@ import io.jsonwebtoken.codec.Decoder;
|
|||
import io.jsonwebtoken.impl.compression.DefaultCompressionCodecResolver;
|
||||
import io.jsonwebtoken.impl.crypto.DefaultJwtSignatureValidator;
|
||||
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.Classes;
|
||||
import io.jsonwebtoken.lang.DateFormats;
|
||||
import io.jsonwebtoken.lang.Objects;
|
||||
import io.jsonwebtoken.lang.Strings;
|
||||
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
import java.io.IOException;
|
||||
import java.security.Key;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
import java.util.Map;
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
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 ObjectMapper objectMapper = new ObjectMapper();
|
||||
|
||||
private byte[] keyBytes;
|
||||
|
||||
private Key key;
|
||||
|
@ -72,12 +70,21 @@ public class DefaultJwtParser implements JwtParser {
|
|||
|
||||
private Decoder<String, byte[]> base64UrlDecoder = Decoder.BASE64URL;
|
||||
|
||||
private Deserializer<Map<String, ?>> deserializer;
|
||||
|
||||
private Claims expectedClaims = new DefaultClaims();
|
||||
|
||||
private Clock clock = DefaultClock.INSTANCE;
|
||||
|
||||
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
|
||||
public JwtParser base64UrlDecodeWith(Decoder<String, byte[]> base64UrlDecoder) {
|
||||
Assert.notNull(base64UrlDecoder, "base64UrlDecoder cannot be null.");
|
||||
|
@ -210,6 +217,13 @@ public class DefaultJwtParser implements JwtParser {
|
|||
@Override
|
||||
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.");
|
||||
|
||||
String base64UrlEncodedHeader = null;
|
||||
|
@ -260,7 +274,7 @@ public class DefaultJwtParser implements JwtParser {
|
|||
if (base64UrlEncodedHeader != null) {
|
||||
byte[] bytes = base64UrlDecoder.decode(base64UrlEncodedHeader);
|
||||
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) {
|
||||
header = new DefaultJwsHeader(m);
|
||||
|
@ -281,7 +295,7 @@ public class DefaultJwtParser implements JwtParser {
|
|||
Claims claims = null;
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
|
@ -369,8 +383,6 @@ public class DefaultJwtParser implements JwtParser {
|
|||
//since 0.3:
|
||||
if (claims != null) {
|
||||
|
||||
SimpleDateFormat sdf;
|
||||
|
||||
final Date now = this.clock.now();
|
||||
long nowTime = now.getTime();
|
||||
|
||||
|
@ -382,9 +394,8 @@ public class DefaultJwtParser implements JwtParser {
|
|||
long maxTime = nowTime - this.allowedClockSkewMillis;
|
||||
Date max = allowSkew ? new Date(maxTime) : now;
|
||||
if (max.after(exp)) {
|
||||
sdf = new SimpleDateFormat(ISO_8601_FORMAT);
|
||||
String expVal = sdf.format(exp);
|
||||
String nowVal = sdf.format(now);
|
||||
String expVal = DateFormats.formatIso8601(exp, false);
|
||||
String nowVal = DateFormats.formatIso8601(now, false);
|
||||
|
||||
long differenceMillis = maxTime - exp.getTime();
|
||||
|
||||
|
@ -403,9 +414,8 @@ public class DefaultJwtParser implements JwtParser {
|
|||
long minTime = nowTime + this.allowedClockSkewMillis;
|
||||
Date min = allowSkew ? new Date(minTime) : now;
|
||||
if (min.before(nbf)) {
|
||||
sdf = new SimpleDateFormat(ISO_8601_FORMAT);
|
||||
String nbfVal = sdf.format(nbf);
|
||||
String nowVal = sdf.format(now);
|
||||
String nbfVal = DateFormats.formatIso8601(nbf, false);
|
||||
String nowVal = DateFormats.formatIso8601(now, false);
|
||||
|
||||
long differenceMillis = nbf.getTime() - minTime;
|
||||
|
||||
|
@ -423,27 +433,37 @@ public class DefaultJwtParser implements JwtParser {
|
|||
Object body = claims != null ? claims : payload;
|
||||
|
||||
if (base64UrlEncodedDigest != null) {
|
||||
return new DefaultJws<Object>((JwsHeader) header, body, base64UrlEncodedDigest);
|
||||
return new DefaultJws<>((JwsHeader) header, body, base64UrlEncodedDigest);
|
||||
} 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) {
|
||||
|
||||
for (String expectedClaimName : expectedClaims.keySet()) {
|
||||
|
||||
Object expectedClaimValue = expectedClaims.get(expectedClaimName);
|
||||
Object actualClaimValue = claims.get(expectedClaimName);
|
||||
Object expectedClaimValue = normalize(expectedClaims.get(expectedClaimName));
|
||||
Object actualClaimValue = normalize(claims.get(expectedClaimName));
|
||||
|
||||
if (Claims.ISSUED_AT.equals(expectedClaimName) || Claims.EXPIRATION.equals(expectedClaimName) ||
|
||||
Claims.NOT_BEFORE.equals(expectedClaimName)) {
|
||||
|
||||
expectedClaimValue = expectedClaims.get(expectedClaimName, Date.class);
|
||||
actualClaimValue = claims.get(expectedClaimName, Date.class);
|
||||
|
||||
} else if (expectedClaimValue instanceof Date && actualClaimValue instanceof Long) {
|
||||
|
||||
actualClaimValue = new Date((Long) actualClaimValue);
|
||||
if (expectedClaimValue instanceof Date) {
|
||||
try {
|
||||
actualClaimValue = claims.get(expectedClaimName, Date.class);
|
||||
} catch (Exception e) {
|
||||
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;
|
||||
throw new IncorrectClaimException(header, claims, msg);
|
||||
}
|
||||
}
|
||||
|
||||
InvalidClaimException invalidClaimException = null;
|
||||
|
@ -553,10 +573,11 @@ public class DefaultJwtParser implements JwtParser {
|
|||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
protected Map<String, Object> readValue(String val) {
|
||||
protected Map<String, ?> readValue(String val) {
|
||||
try {
|
||||
return objectMapper.readValue(val, Map.class);
|
||||
} catch (IOException e) {
|
||||
byte[] bytes = val.getBytes(Strings.UTF_8);
|
||||
return deserializer.deserialize(bytes);
|
||||
} catch (DeserializationException e) {
|
||||
throw new MalformedJwtException("Unable to read JSON value: " + val, e);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,14 +16,17 @@
|
|||
package io.jsonwebtoken.impl;
|
||||
|
||||
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.Date;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
public class JwtMap implements Map<String,Object> {
|
||||
public class JwtMap implements Map<String, Object> {
|
||||
|
||||
private final Map<String, Object> map;
|
||||
|
||||
|
@ -46,23 +49,55 @@ public class JwtMap implements Map<String,Object> {
|
|||
return null;
|
||||
} else if (v instanceof Date) {
|
||||
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) {
|
||||
// https://github.com/jwtk/jjwt/issues/122:
|
||||
// The JWT RFC *mandates* NumericDate values are represented as seconds.
|
||||
// Because Because java.util.Date requires milliseconds, we need to multiply by 1000:
|
||||
long seconds = ((Number) v).longValue();
|
||||
long millis = seconds * 1000;
|
||||
return new Date(millis);
|
||||
v = seconds * 1000;
|
||||
} else if (v instanceof String) {
|
||||
// https://github.com/jwtk/jjwt/issues/122
|
||||
// The JWT RFC *mandates* NumericDate values are represented as seconds.
|
||||
// Because Because java.util.Date requires milliseconds, we need to multiply by 1000:
|
||||
long seconds = Long.parseLong((String) v);
|
||||
long millis = seconds * 1000;
|
||||
return new Date(millis);
|
||||
} else {
|
||||
throw new IllegalStateException("Cannot convert '" + name + "' value [" + v + "] to Date instance.");
|
||||
try {
|
||||
long seconds = Long.parseLong((String) v);
|
||||
v = seconds * 1000;
|
||||
} catch (NumberFormatException ignored) {
|
||||
}
|
||||
}
|
||||
//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) {
|
||||
|
@ -73,12 +108,17 @@ public class JwtMap implements Map<String,Object> {
|
|||
}
|
||||
}
|
||||
|
||||
protected Date getDate(String name) {
|
||||
Object v = map.get(name);
|
||||
return toDate(v, name);
|
||||
@Deprecated //remove just before 1.0.0
|
||||
protected void setDate(String name, Date d) {
|
||||
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) {
|
||||
map.remove(name);
|
||||
} 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
|
||||
* @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);
|
||||
|
||||
|
@ -132,13 +133,13 @@ public final class Classes {
|
|||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public static Object newInstance(String fqcn) {
|
||||
return newInstance(forName(fqcn));
|
||||
public static <T> T newInstance(String fqcn) {
|
||||
return (T)newInstance(forName(fqcn));
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public static Object newInstance(String fqcn, Object... args) {
|
||||
return newInstance(forName(fqcn), args);
|
||||
public static <T> T newInstance(String fqcn, Object... args) {
|
||||
return (T)newInstance(forName(fqcn), args);
|
||||
}
|
||||
|
||||
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 java.security.SecureRandom
|
||||
|
||||
import static org.junit.Assert.*
|
||||
import static ClaimJwtException.INCORRECT_EXPECTED_CLAIM_MESSAGE_TEMPLATE
|
||||
import static ClaimJwtException.MISSING_EXPECTED_CLAIM_MESSAGE_TEMPLATE
|
||||
import static org.junit.Assert.*
|
||||
|
||||
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() {
|
||||
//create random signing key for testing:
|
||||
|
@ -826,15 +826,11 @@ class JwtParserTest {
|
|||
byte[] key = randomKey()
|
||||
|
||||
// not setting expected claim name in JWT
|
||||
String compact = Jwts.builder().signWith(SignatureAlgorithm.HS256, key).
|
||||
setIssuer('Dummy').
|
||||
compact()
|
||||
String compact = Jwts.builder().signWith(SignatureAlgorithm.HS256, key).setIssuer('Dummy').compact()
|
||||
|
||||
try {
|
||||
// expecting null claim name, but with value
|
||||
Jwt<Header, Claims> jwt = Jwts.parser().setSigningKey(key).
|
||||
require(null, expectedClaimValue).
|
||||
parseClaimsJws(compact)
|
||||
Jwts.parser().setSigningKey(key).require(null, expectedClaimValue).parseClaimsJws(compact)
|
||||
fail()
|
||||
} catch (IllegalArgumentException e) {
|
||||
assertEquals(
|
||||
|
@ -876,9 +872,7 @@ class JwtParserTest {
|
|||
byte[] key = randomKey()
|
||||
|
||||
// not setting expected claim name in JWT
|
||||
String compact = Jwts.builder().signWith(SignatureAlgorithm.HS256, key).
|
||||
setIssuer('Dummy').
|
||||
compact()
|
||||
String compact = Jwts.builder().signWith(SignatureAlgorithm.HS256, key).setIssuer('Dummy').compact()
|
||||
|
||||
try {
|
||||
// expecting claim name, but with null value
|
||||
|
@ -964,6 +958,7 @@ class JwtParserTest {
|
|||
|
||||
@Test
|
||||
void testParseRequireIssuedAt_Success() {
|
||||
|
||||
def issuedAt = new Date(System.currentTimeMillis())
|
||||
|
||||
byte[] key = randomKey()
|
||||
|
@ -982,7 +977,7 @@ class JwtParserTest {
|
|||
assertEquals jwt.getBody().getIssuedAt().getTime(), issuedAtMillis, 0
|
||||
}
|
||||
|
||||
@Test
|
||||
@Test(expected = IncorrectClaimException)
|
||||
void testParseRequireIssuedAt_Incorrect_Fail() {
|
||||
def goodIssuedAt = new Date(System.currentTimeMillis())
|
||||
def badIssuedAt = new Date(System.currentTimeMillis() - 10000)
|
||||
|
@ -990,43 +985,27 @@ class JwtParserTest {
|
|||
byte[] key = randomKey()
|
||||
|
||||
String compact = Jwts.builder().signWith(SignatureAlgorithm.HS256, key).
|
||||
setIssuedAt(badIssuedAt).
|
||||
compact()
|
||||
setIssuedAt(badIssuedAt).
|
||||
compact()
|
||||
|
||||
try {
|
||||
Jwts.parser().setSigningKey(key).
|
||||
Jwts.parser().setSigningKey(key).
|
||||
requireIssuedAt(goodIssuedAt).
|
||||
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() {
|
||||
def issuedAt = new Date(System.currentTimeMillis() - 10000)
|
||||
|
||||
byte[] key = randomKey()
|
||||
|
||||
String compact = Jwts.builder().signWith(SignatureAlgorithm.HS256, key).
|
||||
setSubject("Dummy").
|
||||
compact()
|
||||
setSubject("Dummy").
|
||||
compact()
|
||||
|
||||
try {
|
||||
Jwts.parser().setSigningKey(key).
|
||||
Jwts.parser().setSigningKey(key).
|
||||
requireIssuedAt(issuedAt).
|
||||
parseClaimsJws(compact)
|
||||
fail()
|
||||
} catch(MissingClaimException e) {
|
||||
assertEquals(
|
||||
String.format(MISSING_EXPECTED_CLAIM_MESSAGE_TEMPLATE, Claims.ISSUED_AT, issuedAt),
|
||||
e.getMessage()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -1306,7 +1285,7 @@ class JwtParserTest {
|
|||
assertEquals jwt.getBody().getExpiration().getTime(), expirationMillis, 0
|
||||
}
|
||||
|
||||
@Test
|
||||
@Test(expected = IncorrectClaimException)
|
||||
void testParseRequireExpirationAt_Incorrect_Fail() {
|
||||
def goodExpiration = new Date(System.currentTimeMillis() + 20000)
|
||||
def badExpiration = new Date(System.currentTimeMillis() + 10000)
|
||||
|
@ -1314,43 +1293,27 @@ class JwtParserTest {
|
|||
byte[] key = randomKey()
|
||||
|
||||
String compact = Jwts.builder().signWith(SignatureAlgorithm.HS256, key).
|
||||
setExpiration(badExpiration).
|
||||
compact()
|
||||
setExpiration(badExpiration).
|
||||
compact()
|
||||
|
||||
try {
|
||||
Jwts.parser().setSigningKey(key).
|
||||
Jwts.parser().setSigningKey(key).
|
||||
requireExpiration(goodExpiration).
|
||||
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() {
|
||||
def expiration = new Date(System.currentTimeMillis() + 10000)
|
||||
|
||||
byte[] key = randomKey()
|
||||
|
||||
String compact = Jwts.builder().signWith(SignatureAlgorithm.HS256, key).
|
||||
setSubject("Dummy").
|
||||
compact()
|
||||
setSubject("Dummy").
|
||||
compact()
|
||||
|
||||
try {
|
||||
Jwts.parser().setSigningKey(key).
|
||||
Jwts.parser().setSigningKey(key).
|
||||
requireExpiration(expiration).
|
||||
parseClaimsJws(compact)
|
||||
fail()
|
||||
} catch(MissingClaimException e) {
|
||||
assertEquals(
|
||||
String.format(MISSING_EXPECTED_CLAIM_MESSAGE_TEMPLATE, Claims.EXPIRATION, expiration),
|
||||
e.getMessage()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -1374,7 +1337,7 @@ class JwtParserTest {
|
|||
assertEquals jwt.getBody().getNotBefore().getTime(), notBeforeMillis, 0
|
||||
}
|
||||
|
||||
@Test
|
||||
@Test(expected = IncorrectClaimException)
|
||||
void testParseRequireNotBefore_Incorrect_Fail() {
|
||||
def goodNotBefore = new Date(System.currentTimeMillis() - 20000)
|
||||
def badNotBefore = new Date(System.currentTimeMillis() - 10000)
|
||||
|
@ -1382,47 +1345,32 @@ class JwtParserTest {
|
|||
byte[] key = randomKey()
|
||||
|
||||
String compact = Jwts.builder().signWith(SignatureAlgorithm.HS256, key).
|
||||
setNotBefore(badNotBefore).
|
||||
compact()
|
||||
setNotBefore(badNotBefore).
|
||||
compact()
|
||||
|
||||
try {
|
||||
Jwts.parser().setSigningKey(key).
|
||||
Jwts.parser().setSigningKey(key).
|
||||
requireNotBefore(goodNotBefore).
|
||||
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() {
|
||||
def notBefore = new Date(System.currentTimeMillis() - 10000)
|
||||
|
||||
byte[] key = randomKey()
|
||||
|
||||
String compact = Jwts.builder().signWith(SignatureAlgorithm.HS256, key).
|
||||
setSubject("Dummy").
|
||||
compact()
|
||||
setSubject("Dummy").
|
||||
compact()
|
||||
|
||||
try {
|
||||
Jwts.parser().setSigningKey(key).
|
||||
Jwts.parser().setSigningKey(key).
|
||||
requireNotBefore(notBefore).
|
||||
parseClaimsJws(compact)
|
||||
fail()
|
||||
} catch(MissingClaimException e) {
|
||||
assertEquals(
|
||||
String.format(MISSING_EXPECTED_CLAIM_MESSAGE_TEMPLATE, Claims.NOT_BEFORE, notBefore),
|
||||
e.getMessage()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void testParseRequireCustomDate_Success() {
|
||||
|
||||
def aDate = new Date(System.currentTimeMillis())
|
||||
|
||||
byte[] key = randomKey()
|
||||
|
@ -1438,8 +1386,33 @@ class JwtParserTest {
|
|||
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
|
||||
void testParseRequireCustomDate_Incorrect_Fail() {
|
||||
|
||||
def goodDate = new Date(System.currentTimeMillis())
|
||||
def badDate = new Date(System.currentTimeMillis() - 10000)
|
||||
|
||||
|
@ -1460,7 +1433,6 @@ class JwtParserTest {
|
|||
e.getMessage()
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -1534,18 +1506,17 @@ class JwtParserTest {
|
|||
|
||||
String bad = base64Url(header) + '.' + base64Url(payload) + '.' + base64Url(badSig) + '.' + base64Url(bogus)
|
||||
|
||||
|
||||
try {
|
||||
Jwts.parser().setSigningKey(randomKey()).parse(bad)
|
||||
fail()
|
||||
} catch (MalformedJwtException se) {
|
||||
assertEquals 'JWT strings must contain exactly 2 period characters. Found: 3', se.message
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
void testNoHeaderNoSig() {
|
||||
|
||||
String payload = '{"subject":"Joe"}'
|
||||
|
||||
String jwtStr = '.' + base64Url(payload) + '.'
|
||||
|
@ -1575,16 +1546,17 @@ class JwtParserTest {
|
|||
|
||||
@Test
|
||||
void testBadHeaderSig() {
|
||||
|
||||
String header = '{"alg":"none"}'
|
||||
|
||||
String payload = '{"subject":"Joe"}'
|
||||
|
||||
String sig = ";aklsjdf;kajsd;fkjas;dklfj"
|
||||
|
||||
String jwtStr = base64Url(payload) + '.' + base64Url(payload) + '.' + base64Url(sig)
|
||||
String jwtStr = base64Url(header) + '.' + base64Url(payload) + '.' + base64Url(sig)
|
||||
|
||||
try {
|
||||
Jwt jwt = Jwts.parser().parse(jwtStr)
|
||||
Jwts.parser().parse(jwtStr)
|
||||
fail()
|
||||
} catch (MalformedJwtException se) {
|
||||
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
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper
|
||||
import io.jsonwebtoken.codec.Encoder
|
||||
import io.jsonwebtoken.impl.DefaultHeader
|
||||
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.MacProvider
|
||||
import io.jsonwebtoken.impl.crypto.RsaProvider
|
||||
import io.jsonwebtoken.io.impl.RuntimeClasspathSerializerLocator
|
||||
import io.jsonwebtoken.lang.Strings
|
||||
import org.junit.Test
|
||||
|
||||
|
@ -44,6 +44,12 @@ class JwtsTest {
|
|||
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
|
||||
void testSubclass() {
|
||||
new Jwts()
|
||||
|
@ -617,9 +623,8 @@ class JwtsTest {
|
|||
PublicKey publicKey = kp.getPublic();
|
||||
PrivateKey privateKey = kp.getPrivate();
|
||||
|
||||
ObjectMapper om = new ObjectMapper()
|
||||
String header = base64Url(om.writeValueAsString(['alg': 'HS256']))
|
||||
String body = base64Url(om.writeValueAsString('foo'))
|
||||
String header = base64Url(toJson(['alg': 'HS256']))
|
||||
String body = base64Url(toJson('foo'))
|
||||
String compact = header + '.' + body + '.'
|
||||
|
||||
// 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();
|
||||
//PrivateKey privateKey = kp.getPrivate();
|
||||
|
||||
ObjectMapper om = new ObjectMapper()
|
||||
String header = base64Url(om.writeValueAsString(['alg': 'HS256']))
|
||||
String body = base64Url(om.writeValueAsString('foo'))
|
||||
String header = base64Url(toJson(['alg': 'HS256']))
|
||||
String body = base64Url(toJson('foo'))
|
||||
String compact = header + '.' + body + '.'
|
||||
|
||||
// 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();
|
||||
//PrivateKey privateKey = kp.getPrivate();
|
||||
|
||||
ObjectMapper om = new ObjectMapper()
|
||||
String header = base64Url(om.writeValueAsString(['alg': 'HS256']))
|
||||
String body = base64Url(om.writeValueAsString('foo'))
|
||||
String header = base64Url(toJson(['alg': 'HS256']))
|
||||
String body = base64Url(toJson('foo'))
|
||||
String compact = header + '.' + body + '.'
|
||||
|
||||
// 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
|
||||
|
||||
import com.fasterxml.jackson.core.JsonProcessingException
|
||||
import com.fasterxml.jackson.databind.JsonMappingException
|
||||
import io.jsonwebtoken.Jwts
|
||||
import io.jsonwebtoken.SignatureAlgorithm
|
||||
import io.jsonwebtoken.codec.Encoder
|
||||
import io.jsonwebtoken.codec.EncodingException
|
||||
import io.jsonwebtoken.impl.compression.CompressionCodecs
|
||||
import io.jsonwebtoken.impl.crypto.MacProvider
|
||||
import io.jsonwebtoken.io.SerializationException
|
||||
import io.jsonwebtoken.io.impl.orgjson.OrgJsonSerializer
|
||||
import org.junit.Test
|
||||
|
||||
import static org.junit.Assert.*
|
||||
|
@ -181,8 +181,8 @@ class DefaultJwtBuilderTest {
|
|||
|
||||
def b = new DefaultJwtBuilder() {
|
||||
@Override
|
||||
protected byte[] toJson(Object o) throws JsonProcessingException {
|
||||
throw new JsonMappingException('foo')
|
||||
protected byte[] toJson(Object o) throws SerializationException {
|
||||
throw new SerializationException('foo', new Exception())
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -192,16 +192,17 @@ class DefaultJwtBuilderTest {
|
|||
} catch (IllegalStateException ise) {
|
||||
assertEquals ise.cause.message, 'foo'
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
void testCompactCompressionCodecJsonProcessingException() {
|
||||
def b = new DefaultJwtBuilder() {
|
||||
@Override
|
||||
protected byte[] toJson(Object o) throws JsonProcessingException {
|
||||
if (o instanceof DefaultJwsHeader) { return super.toJson(o) }
|
||||
throw new JsonProcessingException('simulate json processing exception on claims')
|
||||
protected byte[] toJson(Object o) throws SerializationException {
|
||||
if (o instanceof DefaultJwsHeader) {
|
||||
return super.toJson(o)
|
||||
}
|
||||
throw new SerializationException('dummy text', new Exception())
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -211,7 +212,7 @@ class DefaultJwtBuilderTest {
|
|||
b.setClaims(c).compressWith(CompressionCodecs.DEFLATE).compact()
|
||||
fail()
|
||||
} 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
|
||||
}
|
||||
|
||||
@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
|
||||
|
||||
import io.jsonwebtoken.Jwts
|
||||
import io.jsonwebtoken.MalformedJwtException
|
||||
import io.jsonwebtoken.SignatureAlgorithm
|
||||
import io.jsonwebtoken.codec.Decoder
|
||||
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 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
|
||||
// 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)
|
||||
void testBase64UrlDecodeWithNullArgument() {
|
||||
new DefaultJwtBuilder().base64UrlEncodeWith(null)
|
||||
new DefaultJwtParser().base64UrlDecodeWith(null)
|
||||
}
|
||||
|
||||
@Test
|
||||
void testBase64UrlEncodeWithCustomEncoder() {
|
||||
void testBase64UrlEncodeWithCustomDecoder() {
|
||||
def decoder = new Decoder() {
|
||||
@Override
|
||||
Object decode(Object o) throws DecodingException {
|
||||
|
@ -27,4 +39,76 @@ class DefaultJwtParserTest {
|
|||
def b = new DefaultJwtParser().base64UrlDecodeWith(decoder)
|
||||
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
|
||||
|
||||
import io.jsonwebtoken.lang.DateFormats
|
||||
import org.junit.Test
|
||||
|
||||
import static org.junit.Assert.*
|
||||
|
||||
class JwtMapTest {
|
||||
|
@ -28,26 +30,98 @@ class JwtMapTest {
|
|||
|
||||
@Test
|
||||
void testToDateFromDate() {
|
||||
|
||||
def d = new Date()
|
||||
|
||||
Date date = JwtMap.toDate(d, 'foo')
|
||||
|
||||
assertSame date, d
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
void testToDateFromString() {
|
||||
|
||||
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.toDate(s, 'foo')
|
||||
|
||||
void testToDateFromCalendar() {
|
||||
def c = Calendar.getInstance(TimeZone.getTimeZone("UTC"))
|
||||
def d = c.getTime()
|
||||
Date date = JwtMap.toDate(c, 'foo')
|
||||
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
|
||||
|
@ -56,7 +130,7 @@ class JwtMapTest {
|
|||
JwtMap.toDate(new Object() { @Override public String toString() {return 'hi'} }, 'foo')
|
||||
fail()
|
||||
} 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
|
||||
public void testEquals() throws Exception {
|
||||
void testEquals() throws Exception {
|
||||
def m1 = new JwtMap();
|
||||
m1.put("a", "a");
|
||||
|
||||
|
@ -137,7 +211,7 @@ class JwtMapTest {
|
|||
}
|
||||
|
||||
@Test
|
||||
public void testHashcode() throws Exception {
|
||||
void testHashcode() throws Exception {
|
||||
def m = new JwtMap();
|
||||
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