diff --git a/README.md b/README.md
index f0a863a8..655a333f 100644
--- a/README.md
+++ b/README.md
@@ -16,7 +16,7 @@ Maven:
io.jsonwebtoken
jjwt
- 0.5.1
+ 0.6
```
@@ -24,7 +24,7 @@ Gradle:
```groovy
dependencies {
- compile 'io.jsonwebtoken:jjwt:0.5.1'
+ compile 'io.jsonwebtoken:jjwt:0.6'
}
```
@@ -99,6 +99,46 @@ These feature sets will be implemented in a future release when possible. Commu
## Release Notes
+### 0.6
+
+#### Enforce JWT Values when Parsing
+
+We added the ability to set expectations when parsing a JWT which ensures particular claims having particular values.
+
+For example, let's say that you require that the JWT you are parsing has a specific `sub` (subject) value,
+otherwise you may not trust the token. You can do that by using one of the `require` methods on the parser builder:
+
+```java
+try {
+ Jwts.parser().requireSubject("jsmith").setSigningKey(key).parseClaimsJws(s);
+} catch(InvalidClaimException ice) {
+ // the sub field was missing or did not have a 'jsmith' value
+}
+```
+
+If it is important to react to a missing vs an incorrect value, instead of catching `InvalidClaimException`, you can catch either `MissingClaimException` or `IncorrectClaimException`:
+
+```java
+try {
+ Jwts.parser().requireSubject("jsmith").setSigningKey(key).parseClaimsJws(s);
+} catch(MissingClaimException mce) {
+ // the parsed JWT did not have the sub field
+} catch(IncorrectClaimException ice) {
+ // the parsed JWT had a sub field, but its value was not equal to 'jsmith'
+}
+```
+
+You can also require custom fields by using the `require(fieldName, requiredFieldValue)` method - for example:
+
+```java
+try {
+ Jwts.parser().require("myfield", "myRequiredValue").setSigningKey(key).parseClaimsJws(s);
+} catch(InvalidClaimException ice) {
+ // the 'myfield' field was missing or did not have a 'myRequiredValue' value
+}
+```
+(or, again, you could catch either MissingClaimException or IncorrectClaimException instead)
+
### 0.5.1
- Minor [bug](https://github.com/jwtk/jjwt/issues/31) fix [release](https://github.com/jwtk/jjwt/issues?q=milestone%3A0.5.1+is%3Aclosed) that ensures correct Base64 padding in Android runtimes.
diff --git a/src/main/java/io/jsonwebtoken/ClaimJwtException.java b/src/main/java/io/jsonwebtoken/ClaimJwtException.java
index 89059b2b..bb7c81fd 100644
--- a/src/main/java/io/jsonwebtoken/ClaimJwtException.java
+++ b/src/main/java/io/jsonwebtoken/ClaimJwtException.java
@@ -22,6 +22,9 @@ package io.jsonwebtoken;
*/
public abstract class ClaimJwtException extends JwtException {
+ public static final String INCORRECT_EXPECTED_CLAIM_MESSAGE_TEMPLATE = "Expected %s claim to be: %s, but was: %s.";
+ public static final String MISSING_EXPECTED_CLAIM_MESSAGE_TEMPLATE = "Expected %s claim to be: %s, but was not present in the JWT claims.";
+
private final Header header;
private final Claims claims;
diff --git a/src/main/java/io/jsonwebtoken/Claims.java b/src/main/java/io/jsonwebtoken/Claims.java
index d510aca8..22a3e364 100644
--- a/src/main/java/io/jsonwebtoken/Claims.java
+++ b/src/main/java/io/jsonwebtoken/Claims.java
@@ -170,4 +170,5 @@ public interface Claims extends Map, ClaimsMutator {
@Override //only for better/targeted JavaDoc
Claims setId(String jti);
+ T get(String claimName, Class requiredType);
}
diff --git a/src/main/java/io/jsonwebtoken/IncorrectClaimException.java b/src/main/java/io/jsonwebtoken/IncorrectClaimException.java
new file mode 100644
index 00000000..860a4d84
--- /dev/null
+++ b/src/main/java/io/jsonwebtoken/IncorrectClaimException.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2015 jsonwebtoken.io
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.jsonwebtoken;
+
+/**
+ * Exception thrown when discovering that a required claim does not equal the required value, indicating the JWT is
+ * invalid and may not be used.
+ *
+ * @since 0.6
+ */
+public class IncorrectClaimException extends InvalidClaimException {
+ public IncorrectClaimException(Header header, Claims claims, String message) {
+ super(header, claims, message);
+ }
+
+ public IncorrectClaimException(Header header, Claims claims, String message, Throwable cause) {
+ super(header, claims, message, cause);
+ }
+}
diff --git a/src/main/java/io/jsonwebtoken/InvalidClaimException.java b/src/main/java/io/jsonwebtoken/InvalidClaimException.java
new file mode 100644
index 00000000..8880792c
--- /dev/null
+++ b/src/main/java/io/jsonwebtoken/InvalidClaimException.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2015 jsonwebtoken.io
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.jsonwebtoken;
+
+/**
+ * Exception indicating a parsed claim is invalid in some way. Subclasses reflect the specific
+ * reason the claim is invalid.
+ *
+ * @see IncorrectClaimException
+ * @see MissingClaimException
+ *
+ * @since 0.6
+ */
+public class InvalidClaimException extends ClaimJwtException {
+ private String claimName;
+ private Object claimValue;
+
+ protected InvalidClaimException(Header header, Claims claims, String message) {
+ super(header, claims, message);
+ }
+
+ protected InvalidClaimException(Header header, Claims claims, String message, Throwable cause) {
+ super(header, claims, message, cause);
+ }
+
+ public String getClaimName() {
+ return claimName;
+ }
+
+ public void setClaimName(String claimName) {
+ this.claimName = claimName;
+ }
+
+ public Object getClaimValue() {
+ return claimValue;
+ }
+
+ public void setClaimValue(Object claimValue) {
+ this.claimValue = claimValue;
+ }
+}
diff --git a/src/main/java/io/jsonwebtoken/JwtParser.java b/src/main/java/io/jsonwebtoken/JwtParser.java
index 909e73d0..c5da9764 100644
--- a/src/main/java/io/jsonwebtoken/JwtParser.java
+++ b/src/main/java/io/jsonwebtoken/JwtParser.java
@@ -16,6 +16,7 @@
package io.jsonwebtoken;
import java.security.Key;
+import java.util.Date;
/**
* A parser for reading JWT strings, used to convert them into a {@link Jwt} object representing the expanded JWT.
@@ -26,6 +27,103 @@ public interface JwtParser {
public static final char SEPARATOR_CHAR = '.';
+ /**
+ * Ensures that the specified {@code jti} exists in the parsed JWT. If missing or if the parsed
+ * value does not equal the specified value, an exception will be thrown indicating that the
+ * JWT is invalid and may not be used.
+ *
+ * @param id
+ * @return the parser method for chaining.
+ * @see MissingClaimException
+ * @see IncorrectClaimException
+ */
+ JwtParser requireId(String id);
+
+ /**
+ * Ensures that the specified {@code sub} exists in the parsed JWT. If missing or if the parsed
+ * value does not equal the specified value, an exception will be thrown indicating that the
+ * JWT is invalid and may not be used.
+ *
+ * @param subject
+ * @return the parser for method chaining.
+ * @see MissingClaimException
+ * @see IncorrectClaimException
+ */
+ JwtParser requireSubject(String subject);
+
+ /**
+ * Ensures that the specified {@code aud} exists in the parsed JWT. If missing or if the parsed
+ * value does not equal the specified value, an exception will be thrown indicating that the
+ * JWT is invalid and may not be used.
+ *
+ * @param audience
+ * @return the parser for method chaining.
+ * @see MissingClaimException
+ * @see IncorrectClaimException
+ */
+ JwtParser requireAudience(String audience);
+
+ /**
+ * Ensures that the specified {@code iss} exists in the parsed JWT. If missing or if the parsed
+ * value does not equal the specified value, an exception will be thrown indicating that the
+ * JWT is invalid and may not be used.
+ *
+ * @param issuer
+ * @return the parser for method chaining.
+ * @see MissingClaimException
+ * @see IncorrectClaimException
+ */
+ JwtParser requireIssuer(String issuer);
+
+ /**
+ * Ensures that the specified {@code iat} exists in the parsed JWT. If missing or if the parsed
+ * value does not equal the specified value, an exception will be thrown indicating that the
+ * JWT is invalid and may not be used.
+ *
+ * @param issuedAt
+ * @return the parser for method chaining.
+ * @see MissingClaimException
+ * @see IncorrectClaimException
+ */
+ JwtParser requireIssuedAt(Date issuedAt);
+
+ /**
+ * Ensures that the specified {@code exp} exists in the parsed JWT. If missing or if the parsed
+ * value does not equal the specified value, an exception will be thrown indicating that the
+ * JWT is invalid and may not be used.
+ *
+ * @param expiration
+ * @return the parser for method chaining.
+ * @see MissingClaimException
+ * @see IncorrectClaimException
+ */
+ JwtParser requireExpiration(Date expiration);
+
+ /**
+ * Ensures that the specified {@code nbf} exists in the parsed JWT. If missing or if the parsed
+ * value does not equal the specified value, an exception will be thrown indicating that the
+ * JWT is invalid and may not be used.
+ *
+ * @param notBefore
+ * @return the parser for method chaining
+ * @see MissingClaimException
+ * @see IncorrectClaimException
+ */
+ JwtParser requireNotBefore(Date notBefore);
+
+ /**
+ * Ensures that the specified {@code claimName} exists in the parsed JWT. If missing or if the parsed
+ * value does not equal the specified value, an exception will be thrown indicating that the
+ * JWT is invalid and may not be used.
+ *
+ * @param claimName
+ * @param value
+ * @return the parser for method chaining.
+ * @see MissingClaimException
+ * @see IncorrectClaimException
+ */
+ JwtParser require(String claimName, Object value);
+
/**
* Sets the signing key used to verify any discovered JWS digital signature. If the specified JWT string is not
* a JWS (no signature), this key is not used.
diff --git a/src/main/java/io/jsonwebtoken/MissingClaimException.java b/src/main/java/io/jsonwebtoken/MissingClaimException.java
new file mode 100644
index 00000000..030fe98d
--- /dev/null
+++ b/src/main/java/io/jsonwebtoken/MissingClaimException.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2015 jsonwebtoken.io
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.jsonwebtoken;
+
+/**
+ * Exception thrown when discovering that a required claim is not present, indicating the JWT is
+ * invalid and may not be used.
+ *
+ * @since 0.6
+ */
+public class MissingClaimException extends InvalidClaimException {
+ public MissingClaimException(Header header, Claims claims, String message) {
+ super(header, claims, message);
+ }
+
+ public MissingClaimException(Header header, Claims claims, String message, Throwable cause) {
+ super(header, claims, message, cause);
+ }
+}
diff --git a/src/main/java/io/jsonwebtoken/RequiredTypeException.java b/src/main/java/io/jsonwebtoken/RequiredTypeException.java
new file mode 100644
index 00000000..eeb60d30
--- /dev/null
+++ b/src/main/java/io/jsonwebtoken/RequiredTypeException.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2014 jsonwebtoken.io
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.jsonwebtoken;
+
+/**
+ * Exception thrown when {@link Claims#get(String, Class)} is called and the value does not match the type of the
+ * {@code Class} argument.
+ *
+ * @since 0.6
+ */
+public class RequiredTypeException extends JwtException {
+ public RequiredTypeException(String message) {
+ super(message);
+ }
+
+ public RequiredTypeException(String message, Throwable cause) {
+ super(message, cause);
+ }
+}
diff --git a/src/main/java/io/jsonwebtoken/impl/DefaultClaims.java b/src/main/java/io/jsonwebtoken/impl/DefaultClaims.java
index 5e6284eb..196c82ff 100644
--- a/src/main/java/io/jsonwebtoken/impl/DefaultClaims.java
+++ b/src/main/java/io/jsonwebtoken/impl/DefaultClaims.java
@@ -16,6 +16,7 @@
package io.jsonwebtoken.impl;
import io.jsonwebtoken.Claims;
+import io.jsonwebtoken.RequiredTypeException;
import java.util.Date;
import java.util.Map;
@@ -65,7 +66,7 @@ public class DefaultClaims extends JwtMap implements Claims {
@Override
public Date getExpiration() {
- return getDate(Claims.EXPIRATION);
+ return get(Claims.EXPIRATION, Date.class);
}
@Override
@@ -76,7 +77,7 @@ public class DefaultClaims extends JwtMap implements Claims {
@Override
public Date getNotBefore() {
- return getDate(Claims.NOT_BEFORE);
+ return get(Claims.NOT_BEFORE, Date.class);
}
@Override
@@ -87,7 +88,7 @@ public class DefaultClaims extends JwtMap implements Claims {
@Override
public Date getIssuedAt() {
- return getDate(Claims.ISSUED_AT);
+ return get(Claims.ISSUED_AT, Date.class);
}
@Override
@@ -106,4 +107,27 @@ public class DefaultClaims extends JwtMap implements Claims {
setValue(Claims.ID, jti);
return this;
}
+
+ @Override
+ public T get(String claimName, Class 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);
+ }
+
+ if (requiredType == Date.class && value instanceof Long) {
+ value = new Date((Long)value);
+ }
+
+ if (!requiredType.isInstance(value)) {
+ throw new RequiredTypeException("Expected value to be of type: " + requiredType + ", but was " + value.getClass());
+ }
+
+ return requiredType.cast(value);
+ }
}
diff --git a/src/main/java/io/jsonwebtoken/impl/DefaultJwtParser.java b/src/main/java/io/jsonwebtoken/impl/DefaultJwtParser.java
index a3b372b2..df5d654f 100644
--- a/src/main/java/io/jsonwebtoken/impl/DefaultJwtParser.java
+++ b/src/main/java/io/jsonwebtoken/impl/DefaultJwtParser.java
@@ -16,13 +16,18 @@
package io.jsonwebtoken.impl;
import com.fasterxml.jackson.databind.ObjectMapper;
+import io.jsonwebtoken.ClaimJwtException;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.CompressionCodec;
import io.jsonwebtoken.CompressionCodecResolver;
import io.jsonwebtoken.ExpiredJwtException;
import io.jsonwebtoken.Header;
+import io.jsonwebtoken.IncorrectClaimException;
+import io.jsonwebtoken.InvalidClaimException;
import io.jsonwebtoken.Jws;
import io.jsonwebtoken.JwsHeader;
+import io.jsonwebtoken.MissingClaimException;
+import io.jsonwebtoken.SigningKeyResolver;
import io.jsonwebtoken.Jwt;
import io.jsonwebtoken.JwtHandler;
import io.jsonwebtoken.JwtHandlerAdapter;
@@ -63,6 +68,66 @@ public class DefaultJwtParser implements JwtParser {
private CompressionCodecResolver compressionCodecResolver = new DefaultCompressionCodecResolver();
+ Claims expectedClaims = new DefaultClaims();
+
+ @Override
+ public JwtParser requireIssuedAt(Date issuedAt) {
+ expectedClaims.setIssuedAt(issuedAt);
+
+ return this;
+ }
+
+ @Override
+ public JwtParser requireIssuer(String issuer) {
+ expectedClaims.setIssuer(issuer);
+
+ return this;
+ }
+
+ @Override
+ public JwtParser requireAudience(String audience) {
+ expectedClaims.setAudience(audience);
+
+ return this;
+ }
+
+ @Override
+ public JwtParser requireSubject(String subject) {
+ expectedClaims.setSubject(subject);
+
+ return this;
+ }
+
+ @Override
+ public JwtParser requireId(String id) {
+ expectedClaims.setId(id);
+
+ return this;
+ }
+
+ @Override
+ public JwtParser requireExpiration(Date expiration) {
+ expectedClaims.setExpiration(expiration);
+
+ return this;
+ }
+
+ @Override
+ public JwtParser requireNotBefore(Date notBefore) {
+ expectedClaims.setNotBefore(notBefore);
+
+ return this;
+ }
+
+ @Override
+ public JwtParser require(String claimName, Object value) {
+ Assert.hasText(claimName, "claim name cannot be null or empty.");
+ Assert.notNull(value, "The value cannot be null for claim name: " + claimName);
+ expectedClaims.put(claimName, value);
+
+ return this;
+ }
+
@Override
public JwtParser setSigningKey(byte[] key) {
Assert.notEmpty(key, "signing key cannot be null or empty.");
@@ -320,6 +385,8 @@ public class DefaultJwtParser implements JwtParser {
throw new PrematureJwtException(header, claims, msg);
}
}
+
+ validateExpectedClaims(header, claims);
}
Object body = claims != null ? claims : payload;
@@ -331,6 +398,51 @@ public class DefaultJwtParser implements JwtParser {
}
}
+ private void validateExpectedClaims(Header header, Claims claims) {
+ for (String expectedClaimName : expectedClaims.keySet()) {
+
+ Object expectedClaimValue = expectedClaims.get(expectedClaimName);
+ Object actualClaimValue = 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 != null &&
+ actualClaimValue instanceof Long
+ ) {
+ actualClaimValue = new Date((Long)actualClaimValue);
+ }
+
+ InvalidClaimException invalidClaimException = null;
+
+ if (actualClaimValue == null) {
+ String msg = String.format(
+ ClaimJwtException.MISSING_EXPECTED_CLAIM_MESSAGE_TEMPLATE,
+ expectedClaimName, expectedClaimValue
+ );
+ invalidClaimException = new MissingClaimException(header, claims, msg);
+ } else if (!expectedClaimValue.equals(actualClaimValue)) {
+ String msg = String.format(
+ ClaimJwtException.INCORRECT_EXPECTED_CLAIM_MESSAGE_TEMPLATE,
+ expectedClaimName, expectedClaimValue, actualClaimValue
+ );
+ invalidClaimException = new IncorrectClaimException(header, claims, msg);
+ }
+
+ if (invalidClaimException != null) {
+ invalidClaimException.setClaimName(expectedClaimName);
+ invalidClaimException.setClaimValue(expectedClaimValue);
+ throw invalidClaimException;
+ }
+ }
+ }
+
/*
* @since 0.5 mostly to allow testing overrides
*/
diff --git a/src/test/groovy/io/jsonwebtoken/IncorrectClaimExceptionTest.groovy b/src/test/groovy/io/jsonwebtoken/IncorrectClaimExceptionTest.groovy
new file mode 100644
index 00000000..3a3062ab
--- /dev/null
+++ b/src/test/groovy/io/jsonwebtoken/IncorrectClaimExceptionTest.groovy
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2015 jsonwebtoken.io
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.jsonwebtoken
+
+import org.junit.Test
+
+import static org.junit.Assert.assertEquals
+import static org.junit.Assert.assertSame
+
+class IncorrectClaimExceptionTest {
+
+ @Test
+ void testOverloadedConstructor() {
+ def header = Jwts.header()
+ def claims = Jwts.claims()
+ def msg = 'foo'
+ def cause = new NullPointerException()
+
+ def claimName = 'cName'
+ def claimValue = 'cValue'
+
+ def ex = new IncorrectClaimException(header, claims, msg, cause)
+ ex.setClaimName(claimName)
+ ex.setClaimValue(claimValue)
+
+ assertSame ex.header, header
+ assertSame ex.claims, claims
+ assertEquals ex.message, msg
+ assertSame ex.cause, cause
+ assertEquals ex.claimName, claimName
+ assertEquals ex.claimValue, claimValue
+ }
+}
diff --git a/src/test/groovy/io/jsonwebtoken/InvalidClaimExceptionTest.groovy b/src/test/groovy/io/jsonwebtoken/InvalidClaimExceptionTest.groovy
new file mode 100644
index 00000000..5209a041
--- /dev/null
+++ b/src/test/groovy/io/jsonwebtoken/InvalidClaimExceptionTest.groovy
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2015 jsonwebtoken.io
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.jsonwebtoken
+
+import org.junit.Test
+
+import static org.junit.Assert.assertEquals
+import static org.junit.Assert.assertSame
+
+class InvalidClaimExceptionTest {
+
+ @Test
+ void testOverloadedConstructor() {
+ def header = Jwts.header()
+ def claims = Jwts.claims()
+ def msg = 'foo'
+ def cause = new NullPointerException()
+
+ def claimName = 'cName'
+ def claimValue = 'cValue'
+
+ def ex = new InvalidClaimException(header, claims, msg, cause)
+ ex.setClaimName(claimName)
+ ex.setClaimValue(claimValue)
+
+ assertSame ex.header, header
+ assertSame ex.claims, claims
+ assertEquals ex.message, msg
+ assertSame ex.cause, cause
+ assertEquals ex.claimName, claimName
+ assertEquals ex.claimValue, claimValue
+ }
+}
diff --git a/src/test/groovy/io/jsonwebtoken/JwtParserTest.groovy b/src/test/groovy/io/jsonwebtoken/JwtParserTest.groovy
index a831c4b1..3e37658f 100644
--- a/src/test/groovy/io/jsonwebtoken/JwtParserTest.groovy
+++ b/src/test/groovy/io/jsonwebtoken/JwtParserTest.groovy
@@ -22,6 +22,8 @@ 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
class JwtParserTest {
@@ -723,4 +725,671 @@ class JwtParserTest {
'method or, for HMAC algorithms, the resolveSigningKeyBytes(JwsHeader, String) method.'
}
}
+
+ @Test
+ void testParseRequireDontAllowNullClaimName() {
+ def expectedClaimValue = 'A Most Awesome Claim Value'
+
+ byte[] key = randomKey()
+
+ // not setting expected claim name in JWT
+ String compact = Jwts.builder().signWith(SignatureAlgorithm.HS256, key).
+ setIssuer('Dummy').
+ compact()
+
+ try {
+ // expecting null claim name, but with value
+ Jwt jwt = Jwts.parser().setSigningKey(key).
+ require(null, expectedClaimValue).
+ parseClaimsJws(compact)
+ fail()
+ } catch (IllegalArgumentException e) {
+ assertEquals(
+ "claim name cannot be null or empty.",
+ e.getMessage()
+ )
+ }
+ }
+
+ @Test
+ void testParseRequireDontAllowEmptyClaimName() {
+ def expectedClaimValue = 'A Most Awesome Claim Value'
+
+ byte[] key = randomKey()
+
+ // not setting expected claim name in JWT
+ String compact = Jwts.builder().signWith(SignatureAlgorithm.HS256, key).
+ setIssuer('Dummy').
+ compact()
+
+ try {
+ // expecting null claim name, but with value
+ Jwt jwt = Jwts.parser().setSigningKey(key).
+ require("", expectedClaimValue).
+ parseClaimsJws(compact)
+ fail()
+ } catch (IllegalArgumentException e) {
+ assertEquals(
+ "claim name cannot be null or empty.",
+ e.getMessage()
+ )
+ }
+ }
+
+ @Test
+ void testParseRequireDontAllowNullClaimValue() {
+ def expectedClaimName = 'A Most Awesome Claim Name'
+
+ byte[] key = randomKey()
+
+ // not setting expected claim name in JWT
+ String compact = Jwts.builder().signWith(SignatureAlgorithm.HS256, key).
+ setIssuer('Dummy').
+ compact()
+
+ try {
+ // expecting claim name, but with null value
+ Jwt jwt = Jwts.parser().setSigningKey(key).
+ require(expectedClaimName, null).
+ parseClaimsJws(compact)
+ fail()
+ } catch (IllegalArgumentException e) {
+ assertEquals(
+ "The value cannot be null for claim name: " + expectedClaimName,
+ e.getMessage()
+ )
+ }
+ }
+
+ @Test
+ void testParseRequireGeneric_Success() {
+ def expectedClaimName = 'A Most Awesome Claim Name'
+ def expectedClaimValue = 'A Most Awesome Claim Value'
+
+ byte[] key = randomKey()
+
+ String compact = Jwts.builder().signWith(SignatureAlgorithm.HS256, key).
+ claim(expectedClaimName, expectedClaimValue).
+ compact()
+
+ Jwt jwt = Jwts.parser().setSigningKey(key).
+ require(expectedClaimName, expectedClaimValue).
+ parseClaimsJws(compact)
+
+ assertEquals jwt.getBody().get(expectedClaimName), expectedClaimValue
+ }
+
+ @Test
+ void testParseRequireGeneric_Incorrect_Fail() {
+ def goodClaimName = 'A Most Awesome Claim Name'
+ def goodClaimValue = 'A Most Awesome Claim Value'
+
+ def badClaimValue = 'A Most Bogus Claim Value'
+
+ byte[] key = randomKey()
+
+ String compact = Jwts.builder().signWith(SignatureAlgorithm.HS256, key).
+ claim(goodClaimName, badClaimValue).
+ compact()
+
+ try {
+ Jwts.parser().setSigningKey(key).
+ require(goodClaimName, goodClaimValue).
+ parseClaimsJws(compact)
+ fail()
+ } catch (IncorrectClaimException e) {
+ assertEquals(
+ String.format(INCORRECT_EXPECTED_CLAIM_MESSAGE_TEMPLATE, goodClaimName, goodClaimValue, badClaimValue),
+ e.getMessage()
+ )
+ }
+ }
+
+ @Test
+ void testParseRequireedGeneric_Missing_Fail() {
+ def claimName = 'A Most Awesome Claim Name'
+ def claimValue = 'A Most Awesome Claim Value'
+
+ byte[] key = randomKey()
+
+ String compact = Jwts.builder().signWith(SignatureAlgorithm.HS256, key).
+ setIssuer('Dummy').
+ compact()
+
+ try {
+ Jwt jwt = Jwts.parser().setSigningKey(key).
+ require(claimName, claimValue).
+ parseClaimsJws(compact)
+ fail()
+ } catch (MissingClaimException e) {
+ assertEquals(
+ String.format(MISSING_EXPECTED_CLAIM_MESSAGE_TEMPLATE, claimName, claimValue),
+ e.getMessage()
+ )
+ }
+ }
+
+ @Test
+ void testParseRequireIssuedAt_Success() {
+ def issuedAt = new Date(System.currentTimeMillis())
+
+ byte[] key = randomKey()
+
+ String compact = Jwts.builder().signWith(SignatureAlgorithm.HS256, key).
+ setIssuedAt(issuedAt).
+ compact()
+
+ Jwt jwt = Jwts.parser().setSigningKey(key).
+ requireIssuedAt(issuedAt).
+ parseClaimsJws(compact)
+
+ // system converts to seconds (lopping off millis precision), then returns millis
+ def issuedAtMillis = ((long)issuedAt.getTime() / 1000) * 1000
+
+ assertEquals jwt.getBody().getIssuedAt().getTime(), issuedAtMillis
+ }
+
+ @Test
+ void testParseRequireIssuedAt_Incorrect_Fail() {
+ def goodIssuedAt = new Date(System.currentTimeMillis())
+ def badIssuedAt = new Date(System.currentTimeMillis() - 10000)
+
+ byte[] key = randomKey()
+
+ String compact = Jwts.builder().signWith(SignatureAlgorithm.HS256, key).
+ setIssuedAt(badIssuedAt).
+ compact()
+
+ try {
+ 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
+ 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()
+
+ try {
+ 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
+ void testParseRequireIssuer_Success() {
+ def issuer = 'A Most Awesome Issuer'
+
+ byte[] key = randomKey()
+
+ String compact = Jwts.builder().signWith(SignatureAlgorithm.HS256, key).
+ setIssuer(issuer).
+ compact()
+
+ Jwt jwt = Jwts.parser().setSigningKey(key).
+ requireIssuer(issuer).
+ parseClaimsJws(compact)
+
+ assertEquals jwt.getBody().getIssuer(), issuer
+ }
+
+ @Test
+ void testParseRequireIssuer_Incorrect_Fail() {
+ def goodIssuer = 'A Most Awesome Issuer'
+ def badIssuer = 'A Most Bogus Issuer'
+
+ byte[] key = randomKey()
+
+ String compact = Jwts.builder().signWith(SignatureAlgorithm.HS256, key).
+ setIssuer(badIssuer).
+ compact()
+
+ try {
+ Jwts.parser().setSigningKey(key).
+ requireIssuer(goodIssuer).
+ parseClaimsJws(compact)
+ fail()
+ } catch(IncorrectClaimException e) {
+ assertEquals(
+ String.format(INCORRECT_EXPECTED_CLAIM_MESSAGE_TEMPLATE, Claims.ISSUER, goodIssuer, badIssuer),
+ e.getMessage()
+ )
+ }
+ }
+
+ @Test
+ void testParseRequireIssuer_Missing_Fail() {
+ def issuer = 'A Most Awesome Issuer'
+
+ byte[] key = randomKey()
+
+ String compact = Jwts.builder().signWith(SignatureAlgorithm.HS256, key).
+ setId('id').
+ compact()
+
+ try {
+ Jwts.parser().setSigningKey(key).
+ requireIssuer(issuer).
+ parseClaimsJws(compact)
+ fail()
+ } catch(MissingClaimException e) {
+ assertEquals(
+ String.format(MISSING_EXPECTED_CLAIM_MESSAGE_TEMPLATE, Claims.ISSUER, issuer),
+ e.getMessage()
+ )
+ }
+ }
+
+ @Test
+ void testParseRequireAudience_Success() {
+ def audience = 'A Most Awesome Audience'
+
+ byte[] key = randomKey()
+
+ String compact = Jwts.builder().signWith(SignatureAlgorithm.HS256, key).
+ setAudience(audience).
+ compact()
+
+ Jwt jwt = Jwts.parser().setSigningKey(key).
+ requireAudience(audience).
+ parseClaimsJws(compact)
+
+ assertEquals jwt.getBody().getAudience(), audience
+ }
+
+ @Test
+ void testParseRequireAudience_Incorrect_Fail() {
+ def goodAudience = 'A Most Awesome Audience'
+ def badAudience = 'A Most Bogus Audience'
+
+ byte[] key = randomKey()
+
+ String compact = Jwts.builder().signWith(SignatureAlgorithm.HS256, key).
+ setAudience(badAudience).
+ compact()
+
+ try {
+ Jwts.parser().setSigningKey(key).
+ requireAudience(goodAudience).
+ parseClaimsJws(compact)
+ fail()
+ } catch(IncorrectClaimException e) {
+ assertEquals(
+ String.format(INCORRECT_EXPECTED_CLAIM_MESSAGE_TEMPLATE, Claims.AUDIENCE, goodAudience, badAudience),
+ e.getMessage()
+ )
+ }
+ }
+
+ @Test
+ void testParseRequireAudience_Missing_Fail() {
+ def audience = 'A Most Awesome audience'
+
+ byte[] key = randomKey()
+
+ String compact = Jwts.builder().signWith(SignatureAlgorithm.HS256, key).
+ setId('id').
+ compact()
+
+ try {
+ Jwts.parser().setSigningKey(key).
+ requireAudience(audience).
+ parseClaimsJws(compact)
+ fail()
+ } catch(MissingClaimException e) {
+ assertEquals(
+ String.format(MISSING_EXPECTED_CLAIM_MESSAGE_TEMPLATE, Claims.AUDIENCE, audience),
+ e.getMessage()
+ )
+ }
+ }
+
+ @Test
+ void testParseRequireSubject_Success() {
+ def subject = 'A Most Awesome Subject'
+
+ byte[] key = randomKey()
+
+ String compact = Jwts.builder().signWith(SignatureAlgorithm.HS256, key).
+ setSubject(subject).
+ compact()
+
+ Jwt jwt = Jwts.parser().setSigningKey(key).
+ requireSubject(subject).
+ parseClaimsJws(compact)
+
+ assertEquals jwt.getBody().getSubject(), subject
+ }
+
+ @Test
+ void testParseRequireSubject_Incorrect_Fail() {
+ def goodSubject = 'A Most Awesome Subject'
+ def badSubject = 'A Most Bogus Subject'
+
+ byte[] key = randomKey()
+
+ String compact = Jwts.builder().signWith(SignatureAlgorithm.HS256, key).
+ setSubject(badSubject).
+ compact()
+
+ try {
+ Jwts.parser().setSigningKey(key).
+ requireSubject(goodSubject).
+ parseClaimsJws(compact)
+ fail()
+ } catch(IncorrectClaimException e) {
+ assertEquals(
+ String.format(INCORRECT_EXPECTED_CLAIM_MESSAGE_TEMPLATE, Claims.SUBJECT, goodSubject, badSubject),
+ e.getMessage()
+ )
+ }
+ }
+
+ @Test
+ void testParseRequireSubject_Missing_Fail() {
+ def subject = 'A Most Awesome Subject'
+
+ byte[] key = randomKey()
+
+ String compact = Jwts.builder().signWith(SignatureAlgorithm.HS256, key).
+ setId('id').
+ compact()
+
+ try {
+ Jwts.parser().setSigningKey(key).
+ requireSubject(subject).
+ parseClaimsJws(compact)
+ fail()
+ } catch(MissingClaimException e) {
+ assertEquals(
+ String.format(MISSING_EXPECTED_CLAIM_MESSAGE_TEMPLATE, Claims.SUBJECT, subject),
+ e.getMessage()
+ )
+ }
+ }
+
+ @Test
+ void testParseRequireId_Success() {
+ def id = 'A Most Awesome id'
+
+ byte[] key = randomKey()
+
+ String compact = Jwts.builder().signWith(SignatureAlgorithm.HS256, key).
+ setId(id).
+ compact()
+
+ Jwt jwt = Jwts.parser().setSigningKey(key).
+ requireId(id).
+ parseClaimsJws(compact)
+
+ assertEquals jwt.getBody().getId(), id
+ }
+
+ @Test
+ void testParseRequireId_Incorrect_Fail() {
+ def goodId = 'A Most Awesome Id'
+ def badId = 'A Most Bogus Id'
+
+ byte[] key = randomKey()
+
+ String compact = Jwts.builder().signWith(SignatureAlgorithm.HS256, key).
+ setId(badId).
+ compact()
+
+ try {
+ Jwts.parser().setSigningKey(key).
+ requireId(goodId).
+ parseClaimsJws(compact)
+ fail()
+ } catch(IncorrectClaimException e) {
+ assertEquals(
+ String.format(INCORRECT_EXPECTED_CLAIM_MESSAGE_TEMPLATE, Claims.ID, goodId, badId),
+ e.getMessage()
+ )
+ }
+ }
+
+ @Test
+ void testParseRequireId_Missing_Fail() {
+ def id = 'A Most Awesome Id'
+
+ byte[] key = randomKey()
+
+ String compact = Jwts.builder().signWith(SignatureAlgorithm.HS256, key).
+ setIssuer('me').
+ compact()
+
+ try {
+ Jwts.parser().setSigningKey(key).
+ requireId(id).
+ parseClaimsJws(compact)
+ fail()
+ } catch(MissingClaimException e) {
+ assertEquals(
+ String.format(MISSING_EXPECTED_CLAIM_MESSAGE_TEMPLATE, Claims.ID, id),
+ e.getMessage()
+ )
+ }
+ }
+
+ @Test
+ void testParseRequireExpiration_Success() {
+ // expire in the future
+ def expiration = new Date(System.currentTimeMillis() + 10000)
+
+ byte[] key = randomKey()
+
+ String compact = Jwts.builder().signWith(SignatureAlgorithm.HS256, key).
+ setExpiration(expiration).
+ compact()
+
+ Jwt jwt = Jwts.parser().setSigningKey(key).
+ requireExpiration(expiration).
+ parseClaimsJws(compact)
+
+ // system converts to seconds (lopping off millis precision), then returns millis
+ def expirationMillis = ((long)expiration.getTime() / 1000) * 1000
+
+ assertEquals jwt.getBody().getExpiration().getTime(), expirationMillis
+ }
+
+ @Test
+ void testParseRequireExpirationAt_Incorrect_Fail() {
+ def goodExpiration = new Date(System.currentTimeMillis() + 20000)
+ def badExpiration = new Date(System.currentTimeMillis() + 10000)
+
+ byte[] key = randomKey()
+
+ String compact = Jwts.builder().signWith(SignatureAlgorithm.HS256, key).
+ setExpiration(badExpiration).
+ compact()
+
+ try {
+ 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
+ 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()
+
+ try {
+ 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
+ void testParseRequireNotBefore_Success() {
+ // expire in the future
+ def notBefore = new Date(System.currentTimeMillis() - 10000)
+
+ byte[] key = randomKey()
+
+ String compact = Jwts.builder().signWith(SignatureAlgorithm.HS256, key).
+ setNotBefore(notBefore).
+ compact()
+
+ Jwt jwt = Jwts.parser().setSigningKey(key).
+ requireNotBefore(notBefore).
+ parseClaimsJws(compact)
+
+ // system converts to seconds (lopping off millis precision), then returns millis
+ def notBeforeMillis = ((long)notBefore.getTime() / 1000) * 1000
+
+ assertEquals jwt.getBody().getNotBefore().getTime(), notBeforeMillis
+ }
+
+ @Test
+ void testParseRequireNotBefore_Incorrect_Fail() {
+ def goodNotBefore = new Date(System.currentTimeMillis() - 20000)
+ def badNotBefore = new Date(System.currentTimeMillis() - 10000)
+
+ byte[] key = randomKey()
+
+ String compact = Jwts.builder().signWith(SignatureAlgorithm.HS256, key).
+ setNotBefore(badNotBefore).
+ compact()
+
+ try {
+ 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
+ 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()
+
+ try {
+ 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()
+
+ String compact = Jwts.builder().signWith(SignatureAlgorithm.HS256, key).
+ claim("aDate", aDate).
+ compact()
+
+ Jwt jwt = Jwts.parser().setSigningKey(key).
+ require("aDate", aDate).
+ parseClaimsJws(compact)
+
+ assertEquals jwt.getBody().get("aDate", Date.class), aDate
+ }
+
+ @Test
+ void testParseRequireCustomDate_Incorrect_Fail() {
+ def goodDate = new Date(System.currentTimeMillis())
+ def badDate = new Date(System.currentTimeMillis() - 10000)
+
+ 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) {
+ assertEquals(
+ String.format(INCORRECT_EXPECTED_CLAIM_MESSAGE_TEMPLATE, "aDate", goodDate, badDate),
+ e.getMessage()
+ )
+ }
+
+ }
+
+ @Test
+ void testParseRequireCustomDate_Missing_Fail() {
+ def aDate = new Date(System.currentTimeMillis())
+
+ byte[] key = randomKey()
+
+ String compact = Jwts.builder().signWith(SignatureAlgorithm.HS256, key).
+ setSubject("Dummy").
+ compact()
+
+ try {
+ Jwts.parser().setSigningKey(key).
+ require("aDate", aDate).
+ parseClaimsJws(compact)
+ fail()
+ } catch(MissingClaimException e) {
+ assertEquals(
+ String.format(MISSING_EXPECTED_CLAIM_MESSAGE_TEMPLATE, "aDate", aDate),
+ e.getMessage()
+ )
+ }
+ }
}
diff --git a/src/test/groovy/io/jsonwebtoken/MissingClaimExceptionTest.groovy b/src/test/groovy/io/jsonwebtoken/MissingClaimExceptionTest.groovy
new file mode 100644
index 00000000..36658df6
--- /dev/null
+++ b/src/test/groovy/io/jsonwebtoken/MissingClaimExceptionTest.groovy
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2015 jsonwebtoken.io
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.jsonwebtoken
+
+import org.junit.Test
+
+import static org.junit.Assert.assertEquals
+import static org.junit.Assert.assertSame
+
+class MissingClaimExceptionTest {
+
+ @Test
+ void testOverloadedConstructor() {
+ def header = Jwts.header()
+ def claims = Jwts.claims()
+ def msg = 'foo'
+ def cause = new NullPointerException()
+
+ def claimName = 'cName'
+ def claimValue = 'cValue'
+
+ def ex = new MissingClaimException(header, claims, msg, cause)
+ ex.setClaimName(claimName)
+ ex.setClaimValue(claimValue)
+
+ assertSame ex.header, header
+ assertSame ex.claims, claims
+ assertEquals ex.message, msg
+ assertSame ex.cause, cause
+ assertEquals ex.claimName, claimName
+ assertEquals ex.claimValue, claimValue
+ }
+}
diff --git a/src/test/groovy/io/jsonwebtoken/RequiredTypeExceptionTest.groovy b/src/test/groovy/io/jsonwebtoken/RequiredTypeExceptionTest.groovy
new file mode 100644
index 00000000..dded945d
--- /dev/null
+++ b/src/test/groovy/io/jsonwebtoken/RequiredTypeExceptionTest.groovy
@@ -0,0 +1,20 @@
+package io.jsonwebtoken
+
+import org.junit.Test
+
+import static org.junit.Assert.assertEquals
+import static org.junit.Assert.assertSame
+
+class RequiredTypeExceptionTest {
+ @Test
+ void testOverloadedConstructor() {
+ def msg = 'foo'
+ def cause = new NullPointerException()
+
+ def ex = new RequiredTypeException(msg, cause)
+
+ assertEquals ex.message, msg
+ assertSame ex.cause, cause
+ }
+
+}
diff --git a/src/test/groovy/io/jsonwebtoken/impl/DefaultClaimsTest.groovy b/src/test/groovy/io/jsonwebtoken/impl/DefaultClaimsTest.groovy
new file mode 100644
index 00000000..161957f0
--- /dev/null
+++ b/src/test/groovy/io/jsonwebtoken/impl/DefaultClaimsTest.groovy
@@ -0,0 +1,103 @@
+/*
+ * Copyright (C) 2015 jsonwebtoken.io
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package io.jsonwebtoken.impl
+
+import io.jsonwebtoken.Claims
+import io.jsonwebtoken.RequiredTypeException
+import org.junit.Before
+import org.junit.Test
+import static org.junit.Assert.*
+
+class DefaultClaimsTest {
+
+ Claims claims
+
+ @Before
+ void setup() {
+ claims = new DefaultClaims()
+ }
+
+ @Test
+ void testGetClaimWithRequiredType_Null_Success() {
+ claims.put("aNull", null)
+ Object result = claims.get("aNull", Integer.class)
+ assertNull(result)
+ }
+
+ @Test
+ void testGetClaimWithRequiredType_Exception() {
+ claims.put("anInteger", new Integer(5))
+ try {
+ claims.get("anInteger", String.class)
+ fail()
+ } catch (RequiredTypeException e) {
+ assertEquals(
+ "Expected value to be of type: class java.lang.String, but was class java.lang.Integer",
+ e.getMessage()
+ )
+ }
+ }
+
+ @Test
+ void testGetClaimWithRequiredType_Success() {
+ claims.put("anInteger", new Integer(5))
+ Object result = claims.get("anInteger", Integer.class)
+
+ assertTrue(result instanceof Integer)
+ }
+
+ @Test
+ void testGetClaimWithRequiredType_Date_Success() {
+ def actual = new Date();
+ claims.put("aDate", actual)
+ Date expected = claims.get("aDate", Date.class);
+ assertEquals(expected, actual)
+ }
+
+ @Test
+ void testGetClaimWithRequiredType_DateWithLong_Success() {
+ def actual = new Date();
+ // note that Long is stored in claim
+ claims.put("aDate", actual.getTime())
+ Date expected = claims.get("aDate", Date.class);
+ assertEquals(expected, actual)
+ }
+
+ @Test
+ void testGetClaimExpiration_Success() {
+ def now = new Date(System.currentTimeMillis())
+ claims.setExpiration(now)
+ Date expected = claims.get("exp", Date.class)
+ assertEquals(expected, claims.getExpiration())
+ }
+
+ @Test
+ void testGetClaimIssuedAt_Success() {
+ def now = new Date(System.currentTimeMillis())
+ claims.setIssuedAt(now)
+ Date expected = claims.get("iat", Date.class)
+ assertEquals(expected, claims.getIssuedAt())
+ }
+
+ @Test
+ void testGetClaimNotBefore_Success() {
+ def now = new Date(System.currentTimeMillis())
+ claims.setNotBefore(now)
+ Date expected = claims.get("nbf", Date.class)
+ assertEquals(expected, claims.getNotBefore())
+ }
+
+}
diff --git a/src/test/groovy/io/jsonwebtoken/impl/JwtMapTest.groovy b/src/test/groovy/io/jsonwebtoken/impl/JwtMapTest.groovy
index 1b155b3e..0d00f6d7 100644
--- a/src/test/groovy/io/jsonwebtoken/impl/JwtMapTest.groovy
+++ b/src/test/groovy/io/jsonwebtoken/impl/JwtMapTest.groovy
@@ -20,6 +20,12 @@ import static org.junit.Assert.*
class JwtMapTest {
+ @Test
+ void testToDateFromNull() {
+ Date actual = JwtMap.toDate(null, 'foo')
+ assertNull actual
+ }
+
@Test
void testToDateFromDate() {