From 73d4e788ef533d1544ee9a949bd44287a35933cb Mon Sep 17 00:00:00 2001
From: josebarrueta
Date: Wed, 26 Aug 2015 16:59:55 -0700
Subject: [PATCH 01/50] Issue 39 - Add generic body type to the DefaultJws
implementation.
---
src/main/java/io/jsonwebtoken/impl/DefaultJws.java | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/main/java/io/jsonwebtoken/impl/DefaultJws.java b/src/main/java/io/jsonwebtoken/impl/DefaultJws.java
index f8298a89..f1b4958b 100644
--- a/src/main/java/io/jsonwebtoken/impl/DefaultJws.java
+++ b/src/main/java/io/jsonwebtoken/impl/DefaultJws.java
@@ -18,7 +18,7 @@ package io.jsonwebtoken.impl;
import io.jsonwebtoken.Jws;
import io.jsonwebtoken.JwsHeader;
-public class DefaultJws implements Jws {
+public class DefaultJws implements Jws {
private final JwsHeader header;
private final B body;
From 5c664358251b6fae123d61120a5d221ef8c3071f Mon Sep 17 00:00:00 2001
From: josebarrueta
Date: Wed, 26 Aug 2015 17:07:41 -0700
Subject: [PATCH 02/50] Issue-34 getting rid of slf4j.
---
pom.xml | 7 ------
.../java/io/jsonwebtoken/lang/Classes.java | 24 ++-----------------
2 files changed, 2 insertions(+), 29 deletions(-)
diff --git a/pom.xml b/pom.xml
index 34923813..3cd8715f 100644
--- a/pom.xml
+++ b/pom.xml
@@ -61,7 +61,6 @@
UTF-8
${user.name}-${maven.build.timestamp}
- 1.7.6
2.4.2
@@ -78,12 +77,6 @@
-
-
- org.slf4j
- slf4j-api
- ${slf4j.version}
-
com.fasterxml.jackson.core
jackson-databind
diff --git a/src/main/java/io/jsonwebtoken/lang/Classes.java b/src/main/java/io/jsonwebtoken/lang/Classes.java
index b8a62e1b..e031d792 100644
--- a/src/main/java/io/jsonwebtoken/lang/Classes.java
+++ b/src/main/java/io/jsonwebtoken/lang/Classes.java
@@ -15,9 +15,6 @@
*/
package io.jsonwebtoken.lang;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
import java.io.InputStream;
import java.lang.reflect.Constructor;
@@ -26,11 +23,6 @@ import java.lang.reflect.Constructor;
*/
public class Classes {
- /**
- * Private internal log instance.
- */
- private static final Logger log = LoggerFactory.getLogger(Classes.class);
-
/**
* @since 0.1
*/
@@ -78,18 +70,10 @@ public class Classes {
Class clazz = THREAD_CL_ACCESSOR.loadClass(fqcn);
if (clazz == null) {
- if (log.isTraceEnabled()) {
- log.trace("Unable to load class named [" + fqcn +
- "] from the thread context ClassLoader. Trying the current ClassLoader...");
- }
clazz = CLASS_CL_ACCESSOR.loadClass(fqcn);
}
if (clazz == null) {
- if (log.isTraceEnabled()) {
- log.trace("Unable to load class named [" + fqcn + "] from the current ClassLoader. " +
- "Trying the system/application ClassLoader...");
- }
clazz = SYSTEM_CL_ACCESSOR.loadClass(fqcn);
}
@@ -212,9 +196,7 @@ public class Classes {
try {
clazz = cl.loadClass(fqcn);
} catch (ClassNotFoundException e) {
- if (log.isTraceEnabled()) {
- log.trace("Unable to load clazz named [" + fqcn + "] from class loader [" + cl + "]");
- }
+ //Class couldn't be found by loader
}
}
return clazz;
@@ -233,9 +215,7 @@ public class Classes {
try {
return doGetClassLoader();
} catch (Throwable t) {
- if (log.isDebugEnabled()) {
- log.debug("Unable to acquire ClassLoader.", t);
- }
+ //Unable to get ClassLoader
}
return null;
}
From 59993cf61438287ffce327e62be51038a5193ab7 Mon Sep 17 00:00:00 2001
From: Micah Silverman
Date: Wed, 9 Sep 2015 22:50:28 -0400
Subject: [PATCH 03/50] Fixed broken testParseClaimsJwsWithPlaintextJws test.
---
.../io/jsonwebtoken/JwtParserTest.groovy | 33 ++++++++++++-------
1 file changed, 22 insertions(+), 11 deletions(-)
diff --git a/src/test/groovy/io/jsonwebtoken/JwtParserTest.groovy b/src/test/groovy/io/jsonwebtoken/JwtParserTest.groovy
index 490099e8..f5734048 100644
--- a/src/test/groovy/io/jsonwebtoken/JwtParserTest.groovy
+++ b/src/test/groovy/io/jsonwebtoken/JwtParserTest.groovy
@@ -167,7 +167,8 @@ class JwtParserTest {
String compact = Jwts.builder().setSubject('Joe').setExpiration(exp).compact();
try {
- Jwts.parser().parse(compact);
+ Jwts.parser().parse(compact)
+ fail()
} catch (ExpiredJwtException e) {
assertTrue e.getMessage().startsWith('JWT expired at ')
}
@@ -181,7 +182,8 @@ class JwtParserTest {
String compact = Jwts.builder().setSubject('Joe').setNotBefore(nbf).compact();
try {
- Jwts.parser().parse(compact);
+ Jwts.parser().parse(compact)
+ fail()
} catch (PrematureJwtException e) {
assertTrue e.getMessage().startsWith('JWT must not be accepted before ')
}
@@ -268,7 +270,8 @@ class JwtParserTest {
String compact = Jwts.builder().setPayload(payload).compact()
try {
- Jwts.parser().parseClaimsJwt(compact);
+ Jwts.parser().parseClaimsJwt(compact)
+ fail()
} catch (UnsupportedJwtException e) {
assertEquals e.getMessage(), 'Unsigned plaintext JWTs are not supported.'
}
@@ -327,7 +330,8 @@ class JwtParserTest {
String compact = Jwts.builder().setSubject('Joe').setNotBefore(nbf).compact();
try {
- Jwts.parser().parseClaimsJwt(compact);
+ Jwts.parser().parseClaimsJwt(compact)
+ fail()
} catch (PrematureJwtException e) {
assertTrue e.getMessage().startsWith('JWT must not be accepted before ')
}
@@ -361,7 +365,8 @@ class JwtParserTest {
String compact = Jwts.builder().setPayload(payload).compact()
try {
- Jwts.parser().setSigningKey(key).parsePlaintextJws(compact);
+ Jwts.parser().setSigningKey(key).parsePlaintextJws(compact)
+ fail()
} catch (UnsupportedJwtException e) {
assertEquals e.getMessage(), 'Unsigned plaintext JWTs are not supported.'
}
@@ -377,7 +382,8 @@ class JwtParserTest {
String compact = Jwts.builder().setSubject(subject).compact()
try {
- Jwts.parser().setSigningKey(key).parsePlaintextJws(compact);
+ Jwts.parser().setSigningKey(key).parsePlaintextJws(compact)
+ fail()
} catch (UnsupportedJwtException e) {
assertEquals e.getMessage(), 'Unsigned Claims JWTs are not supported.'
}
@@ -393,7 +399,8 @@ class JwtParserTest {
String compact = Jwts.builder().setSubject(subject).signWith(SignatureAlgorithm.HS256, key).compact()
try {
- Jwts.parser().setSigningKey(key).parsePlaintextJws(compact);
+ Jwts.parser().setSigningKey(key).parsePlaintextJws(compact)
+ fail()
} catch (UnsupportedJwtException e) {
assertEquals e.getMessage(), 'Signed Claims JWSs are not supported.'
}
@@ -452,7 +459,8 @@ class JwtParserTest {
String compact = Jwts.builder().setSubject(sub).setNotBefore(nbf).signWith(SignatureAlgorithm.HS256, key).compact();
try {
- Jwts.parser().setSigningKey(key).parseClaimsJws(compact);
+ Jwts.parser().setSigningKey(key).parseClaimsJws(compact)
+ fail()
} catch (PrematureJwtException e) {
assertTrue e.getMessage().startsWith('JWT must not be accepted before ')
assertEquals e.getClaims().getSubject(), sub
@@ -470,7 +478,8 @@ class JwtParserTest {
String compact = Jwts.builder().setPayload(payload).compact()
try {
- Jwts.parser().setSigningKey(key).parseClaimsJws(compact);
+ Jwts.parser().setSigningKey(key).parseClaimsJws(compact)
+ fail()
} catch (UnsupportedJwtException e) {
assertEquals e.getMessage(), 'Unsigned plaintext JWTs are not supported.'
}
@@ -486,7 +495,8 @@ class JwtParserTest {
String compact = Jwts.builder().setSubject(subject).compact()
try {
- Jwts.parser().setSigningKey(key).parseClaimsJws(compact);
+ Jwts.parser().setSigningKey(key).parseClaimsJws(compact)
+ fail()
} catch (UnsupportedJwtException e) {
assertEquals e.getMessage(), 'Unsigned Claims JWTs are not supported.'
}
@@ -502,7 +512,8 @@ class JwtParserTest {
String compact = Jwts.builder().setSubject(subject).signWith(SignatureAlgorithm.HS256, key).compact()
try {
- Jwts.parser().setSigningKey(key).parseClaimsJws(compact);
+ Jwts.parser().setSigningKey(key).parsePlaintextJws(compact)
+ fail()
} catch (UnsupportedJwtException e) {
assertEquals e.getMessage(), 'Signed Claims JWSs are not supported.'
}
From bdb7597f0ae4dcf307b823d9b15b845537d777f0 Mon Sep 17 00:00:00 2001
From: Micah Silverman
Date: Wed, 9 Sep 2015 23:22:17 -0400
Subject: [PATCH 04/50] Got rid of semicolons in JwtParserTest to make it
consistent throughout.
---
.../io/jsonwebtoken/JwtParserTest.groovy | 114 +++++++++---------
1 file changed, 57 insertions(+), 57 deletions(-)
diff --git a/src/test/groovy/io/jsonwebtoken/JwtParserTest.groovy b/src/test/groovy/io/jsonwebtoken/JwtParserTest.groovy
index f5734048..a831c4b1 100644
--- a/src/test/groovy/io/jsonwebtoken/JwtParserTest.groovy
+++ b/src/test/groovy/io/jsonwebtoken/JwtParserTest.groovy
@@ -29,17 +29,17 @@ class JwtParserTest {
protected static byte[] randomKey() {
//create random signing key for testing:
- byte[] key = new byte[64];
- random.nextBytes(key);
- return key;
+ byte[] key = new byte[64]
+ random.nextBytes(key)
+ return key
}
@Test
void testSetDuplicateSigningKeys() {
- byte[] keyBytes = randomKey();
+ byte[] keyBytes = randomKey()
- SecretKeySpec key = new SecretKeySpec(keyBytes, "HmacSHA256");
+ SecretKeySpec key = new SecretKeySpec(keyBytes, "HmacSHA256")
String compact = Jwts.builder().setPayload('Hello World!').signWith(SignatureAlgorithm.HS256, keyBytes).compact()
@@ -58,7 +58,7 @@ class JwtParserTest {
@Test
void testIsSignedWithJunkArgument() {
- assertFalse Jwts.parser().isSigned('hello');
+ assertFalse Jwts.parser().isSigned('hello')
}
@Test
@@ -67,10 +67,10 @@ class JwtParserTest {
String junkPayload = '{;aklsjd;fkajsd;fkjasd;lfkj}'
String bad = TextCodec.BASE64.encode('{"alg":"none"}') + '.' +
- TextCodec.BASE64.encode(junkPayload) + '.';
+ TextCodec.BASE64.encode(junkPayload) + '.'
try {
- Jwts.parser().parse(bad);
+ Jwts.parser().parse(bad)
fail()
} catch (MalformedJwtException expected) {
assertEquals expected.getMessage(), 'Unable to read JSON value: ' + junkPayload
@@ -82,7 +82,7 @@ class JwtParserTest {
String badAlgorithmName = 'whatever'
- String header = "{\"alg\":\"$badAlgorithmName\"}";
+ String header = "{\"alg\":\"$badAlgorithmName\"}"
String payload = '{"subject":"Joe"}'
@@ -90,10 +90,10 @@ class JwtParserTest {
String bad = TextCodec.BASE64.encode(header) + '.' +
TextCodec.BASE64.encode(payload) + '.' +
- TextCodec.BASE64.encode(badSig);
+ TextCodec.BASE64.encode(badSig)
try {
- Jwts.parser().setSigningKey(randomKey()).parse(bad);
+ Jwts.parser().setSigningKey(randomKey()).parse(bad)
fail()
} catch (SignatureException se) {
assertEquals se.getMessage(), "Unsupported signature algorithm '$badAlgorithmName'".toString()
@@ -111,10 +111,10 @@ class JwtParserTest {
String bad = TextCodec.BASE64.encode(header) + '.' +
TextCodec.BASE64.encode(payload) + '.' +
- TextCodec.BASE64.encode(badSig);
+ TextCodec.BASE64.encode(badSig)
try {
- Jwts.parser().setSigningKey(randomKey()).parse(bad);
+ Jwts.parser().setSigningKey(randomKey()).parse(bad)
fail()
} catch (SignatureException se) {
assertEquals se.getMessage(), 'JWT signature does not match locally computed signature. JWT validity cannot be asserted and should not be trusted.'
@@ -133,10 +133,10 @@ class JwtParserTest {
String bad = TextCodec.BASE64.encode(header) + '.' +
TextCodec.BASE64.encode(payload) + '.' +
- TextCodec.BASE64.encode(badSig);
+ TextCodec.BASE64.encode(badSig)
try {
- Jwts.parser().setSigningKey(randomKey()).parse(bad);
+ Jwts.parser().setSigningKey(randomKey()).parse(bad)
fail()
} catch (MalformedJwtException se) {
assertEquals se.getMessage(), 'JWT string has a digest/signature, but the header does not reference a valid signature algorithm.'
@@ -146,8 +146,8 @@ class JwtParserTest {
@Test
void testParseWithBase64EncodedSigningKey() {
- byte[] key = randomKey();
- String base64Encodedkey = TextCodec.BASE64.encode(key);
+ byte[] key = randomKey()
+ String base64Encodedkey = TextCodec.BASE64.encode(key)
String payload = 'Hello world!'
String compact = Jwts.builder().setPayload(payload).signWith(SignatureAlgorithm.HS256, base64Encodedkey).compact()
@@ -162,9 +162,9 @@ class JwtParserTest {
@Test
void testParseWithExpiredJwt() {
- Date exp = new Date(System.currentTimeMillis() - 1000);
+ Date exp = new Date(System.currentTimeMillis() - 1000)
- String compact = Jwts.builder().setSubject('Joe').setExpiration(exp).compact();
+ String compact = Jwts.builder().setSubject('Joe').setExpiration(exp).compact()
try {
Jwts.parser().parse(compact)
@@ -177,9 +177,9 @@ class JwtParserTest {
@Test
void testParseWithPrematureJwt() {
- Date nbf = new Date(System.currentTimeMillis() + 100000);
+ Date nbf = new Date(System.currentTimeMillis() + 100000)
- String compact = Jwts.builder().setSubject('Joe').setNotBefore(nbf).compact();
+ String compact = Jwts.builder().setSubject('Joe').setNotBefore(nbf).compact()
try {
Jwts.parser().parse(compact)
@@ -200,7 +200,7 @@ class JwtParserTest {
String compact = Jwts.builder().setPayload(payload).compact()
- Jwt jwt = Jwts.parser().parsePlaintextJwt(compact);
+ Jwt jwt = Jwts.parser().parsePlaintextJwt(compact)
assertEquals jwt.getBody(), payload
}
@@ -208,11 +208,11 @@ class JwtParserTest {
@Test
void testParsePlaintextJwtWithClaimsJwt() {
- String compact = Jwts.builder().setSubject('Joe').compact();
+ String compact = Jwts.builder().setSubject('Joe').compact()
try {
- Jwts.parser().parsePlaintextJwt(compact);
- fail();
+ Jwts.parser().parsePlaintextJwt(compact)
+ fail()
} catch (UnsupportedJwtException e) {
assertEquals e.getMessage(), 'Unsigned Claims JWTs are not supported.'
}
@@ -226,8 +226,8 @@ class JwtParserTest {
String compact = Jwts.builder().setPayload(payload).signWith(SignatureAlgorithm.HS256, randomKey()).compact()
try {
- Jwts.parser().parsePlaintextJws(compact);
- fail();
+ Jwts.parser().parsePlaintextJws(compact)
+ fail()
} catch (UnsupportedJwtException e) {
assertEquals e.getMessage(), 'Signed JWSs are not supported.'
}
@@ -239,8 +239,8 @@ class JwtParserTest {
String compact = Jwts.builder().setSubject('Joe').signWith(SignatureAlgorithm.HS256, randomKey()).compact()
try {
- Jwts.parser().parsePlaintextJws(compact);
- fail();
+ Jwts.parser().parsePlaintextJws(compact)
+ fail()
} catch (UnsupportedJwtException e) {
assertEquals e.getMessage(), 'Signed JWSs are not supported.'
}
@@ -257,7 +257,7 @@ class JwtParserTest {
String compact = Jwts.builder().setSubject(subject).compact()
- Jwt jwt = Jwts.parser().parseClaimsJwt(compact);
+ Jwt jwt = Jwts.parser().parseClaimsJwt(compact)
assertEquals jwt.getBody().getSubject(), subject
}
@@ -285,8 +285,8 @@ class JwtParserTest {
String compact = Jwts.builder().setPayload(payload).signWith(SignatureAlgorithm.HS256, randomKey()).compact()
try {
- Jwts.parser().parseClaimsJwt(compact);
- fail();
+ Jwts.parser().parseClaimsJwt(compact)
+ fail()
} catch (UnsupportedJwtException e) {
assertEquals e.getMessage(), 'Signed JWSs are not supported.'
}
@@ -298,8 +298,8 @@ class JwtParserTest {
String compact = Jwts.builder().setSubject('Joe').signWith(SignatureAlgorithm.HS256, randomKey()).compact()
try {
- Jwts.parser().parseClaimsJwt(compact);
- fail();
+ Jwts.parser().parseClaimsJwt(compact)
+ fail()
} catch (UnsupportedJwtException e) {
assertEquals e.getMessage(), 'Signed JWSs are not supported.'
}
@@ -308,15 +308,15 @@ class JwtParserTest {
@Test
void testParseClaimsJwtWithExpiredJwt() {
- long nowMillis = System.currentTimeMillis();
+ long nowMillis = System.currentTimeMillis()
//some time in the past:
- Date exp = new Date(nowMillis - 1000);
+ Date exp = new Date(nowMillis - 1000)
String compact = Jwts.builder().setSubject('Joe').setExpiration(exp).compact()
try {
- Jwts.parser().parseClaimsJwt(compact);
- fail();
+ Jwts.parser().parseClaimsJwt(compact)
+ fail()
} catch (ExpiredJwtException e) {
assertTrue e.getMessage().startsWith('JWT expired at ')
}
@@ -325,9 +325,9 @@ class JwtParserTest {
@Test
void testParseClaimsJwtWithPrematureJwt() {
- Date nbf = new Date(System.currentTimeMillis() + 100000);
+ Date nbf = new Date(System.currentTimeMillis() + 100000)
- String compact = Jwts.builder().setSubject('Joe').setNotBefore(nbf).compact();
+ String compact = Jwts.builder().setSubject('Joe').setNotBefore(nbf).compact()
try {
Jwts.parser().parseClaimsJwt(compact)
@@ -350,7 +350,7 @@ class JwtParserTest {
String compact = Jwts.builder().setPayload(payload).signWith(SignatureAlgorithm.HS256, key).compact()
- Jwt jwt = Jwts.parser().setSigningKey(key).parsePlaintextJws(compact);
+ Jwt jwt = Jwts.parser().setSigningKey(key).parsePlaintextJws(compact)
assertEquals jwt.getBody(), payload
}
@@ -419,7 +419,7 @@ class JwtParserTest {
String compact = Jwts.builder().setSubject(sub).signWith(SignatureAlgorithm.HS256, key).compact()
- Jwt jwt = Jwts.parser().setSigningKey(key).parseClaimsJws(compact);
+ Jwt jwt = Jwts.parser().setSigningKey(key).parseClaimsJws(compact)
assertEquals jwt.getBody().getSubject(), sub
}
@@ -431,15 +431,15 @@ class JwtParserTest {
byte[] key = randomKey()
- long nowMillis = System.currentTimeMillis();
+ long nowMillis = System.currentTimeMillis()
//some time in the past:
- Date exp = new Date(nowMillis - 1000);
+ Date exp = new Date(nowMillis - 1000)
String compact = Jwts.builder().setSubject(sub).signWith(SignatureAlgorithm.HS256, key).setExpiration(exp).compact()
try {
- Jwts.parser().setSigningKey(key).parseClaimsJwt(compact);
- fail();
+ Jwts.parser().setSigningKey(key).parseClaimsJwt(compact)
+ fail()
} catch (ExpiredJwtException e) {
assertTrue e.getMessage().startsWith('JWT expired at ')
assertEquals e.getClaims().getSubject(), sub
@@ -454,9 +454,9 @@ class JwtParserTest {
byte[] key = randomKey()
- Date nbf = new Date(System.currentTimeMillis() + 100000);
+ Date nbf = new Date(System.currentTimeMillis() + 100000)
- String compact = Jwts.builder().setSubject(sub).setNotBefore(nbf).signWith(SignatureAlgorithm.HS256, key).compact();
+ String compact = Jwts.builder().setSubject(sub).setNotBefore(nbf).signWith(SignatureAlgorithm.HS256, key).compact()
try {
Jwts.parser().setSigningKey(key).parseClaimsJws(compact)
@@ -561,7 +561,7 @@ class JwtParserTest {
}
try {
- Jwts.parser().setSigningKeyResolver(signingKeyResolver).parseClaimsJws(compact);
+ Jwts.parser().setSigningKeyResolver(signingKeyResolver).parseClaimsJws(compact)
fail()
} catch (SignatureException se) {
assertEquals se.getMessage(), 'JWT signature does not match locally computed signature. JWT validity cannot be asserted and should not be trusted.'
@@ -573,7 +573,7 @@ class JwtParserTest {
String subject = 'Joe'
- SecretKeySpec key = new SecretKeySpec(randomKey(), "HmacSHA256");
+ SecretKeySpec key = new SecretKeySpec(randomKey(), "HmacSHA256")
String compact = Jwts.builder().setSubject(subject).signWith(SignatureAlgorithm.HS256, key).compact()
@@ -585,7 +585,7 @@ class JwtParserTest {
}
try {
- Jwts.parser().setSigningKey(key).setSigningKeyResolver(signingKeyResolver).parseClaimsJws(compact);
+ Jwts.parser().setSigningKey(key).setSigningKeyResolver(signingKeyResolver).parseClaimsJws(compact)
fail()
} catch (IllegalStateException ise) {
assertEquals ise.getMessage(), 'A signing key resolver and a key object cannot both be specified. Choose either.'
@@ -609,7 +609,7 @@ class JwtParserTest {
}
try {
- Jwts.parser().setSigningKey(key).setSigningKeyResolver(signingKeyResolver).parseClaimsJws(compact);
+ Jwts.parser().setSigningKey(key).setSigningKeyResolver(signingKeyResolver).parseClaimsJws(compact)
fail()
} catch (IllegalStateException ise) {
assertEquals ise.getMessage(), 'A signing key resolver and key bytes cannot both be specified. Choose either.'
@@ -626,7 +626,7 @@ class JwtParserTest {
String compact = Jwts.builder().setSubject(subject).signWith(SignatureAlgorithm.HS256, key).compact()
try {
- Jwts.parser().setSigningKeyResolver(null).parseClaimsJws(compact);
+ Jwts.parser().setSigningKeyResolver(null).parseClaimsJws(compact)
fail()
} catch (IllegalArgumentException iae) {
assertEquals iae.getMessage(), 'SigningKeyResolver cannot be null.'
@@ -645,7 +645,7 @@ class JwtParserTest {
def signingKeyResolver = new SigningKeyResolverAdapter()
try {
- Jwts.parser().setSigningKeyResolver(signingKeyResolver).parseClaimsJws(compact);
+ Jwts.parser().setSigningKeyResolver(signingKeyResolver).parseClaimsJws(compact)
fail()
} catch (UnsupportedJwtException ex) {
assertEquals ex.getMessage(), 'The specified SigningKeyResolver implementation does not support ' +
@@ -674,7 +674,7 @@ class JwtParserTest {
}
}
- Jws jws = Jwts.parser().setSigningKeyResolver(signingKeyResolver).parsePlaintextJws(compact);
+ Jws jws = Jwts.parser().setSigningKeyResolver(signingKeyResolver).parsePlaintextJws(compact)
assertEquals jws.getBody(), inputPayload
}
@@ -696,7 +696,7 @@ class JwtParserTest {
}
try {
- Jwts.parser().setSigningKeyResolver(signingKeyResolver).parsePlaintextJws(compact);
+ Jwts.parser().setSigningKeyResolver(signingKeyResolver).parsePlaintextJws(compact)
fail()
} catch (SignatureException se) {
assertEquals se.getMessage(), 'JWT signature does not match locally computed signature. JWT validity cannot be asserted and should not be trusted.'
@@ -715,7 +715,7 @@ class JwtParserTest {
def signingKeyResolver = new SigningKeyResolverAdapter()
try {
- Jwts.parser().setSigningKeyResolver(signingKeyResolver).parsePlaintextJws(compact);
+ Jwts.parser().setSigningKeyResolver(signingKeyResolver).parsePlaintextJws(compact)
fail()
} catch (UnsupportedJwtException ex) {
assertEquals ex.getMessage(), 'The specified SigningKeyResolver implementation does not support plaintext ' +
From 16b970d28efa6f0b91428cf58fb70e3746ca44ed Mon Sep 17 00:00:00 2001
From: Les Hazlewood
Date: Wed, 9 Sep 2015 21:14:59 -0700
Subject: [PATCH 05/50] Fixes #35. Also enhanced some test code coverage.
---
pom.xml | 5 --
.../impl/crypto/MacValidator.java | 3 +-
.../impl/Base64UrlCodecTest.groovy | 19 ++++++
.../impl/DefaultJwtBuilderTest.groovy | 61 +++++++++++++++++++
4 files changed, 82 insertions(+), 6 deletions(-)
create mode 100644 src/test/groovy/io/jsonwebtoken/impl/Base64UrlCodecTest.groovy
diff --git a/pom.xml b/pom.xml
index 3cd8715f..057dea65 100644
--- a/pom.xml
+++ b/pom.xml
@@ -296,11 +296,6 @@
96
100
-
- io.jsonwebtoken.impl.Base64UrlCodec
- 100
- 95
-
io.jsonwebtoken.impl.DefaultJwtBuilder
91
diff --git a/src/main/java/io/jsonwebtoken/impl/crypto/MacValidator.java b/src/main/java/io/jsonwebtoken/impl/crypto/MacValidator.java
index 95227ae9..af319666 100644
--- a/src/main/java/io/jsonwebtoken/impl/crypto/MacValidator.java
+++ b/src/main/java/io/jsonwebtoken/impl/crypto/MacValidator.java
@@ -18,6 +18,7 @@ package io.jsonwebtoken.impl.crypto;
import io.jsonwebtoken.SignatureAlgorithm;
import java.security.Key;
+import java.security.MessageDigest;
import java.util.Arrays;
public class MacValidator implements SignatureValidator {
@@ -31,6 +32,6 @@ public class MacValidator implements SignatureValidator {
@Override
public boolean isValid(byte[] data, byte[] signature) {
byte[] computed = this.signer.sign(data);
- return Arrays.equals(computed, signature);
+ return MessageDigest.isEqual(computed, signature);
}
}
diff --git a/src/test/groovy/io/jsonwebtoken/impl/Base64UrlCodecTest.groovy b/src/test/groovy/io/jsonwebtoken/impl/Base64UrlCodecTest.groovy
new file mode 100644
index 00000000..45969d86
--- /dev/null
+++ b/src/test/groovy/io/jsonwebtoken/impl/Base64UrlCodecTest.groovy
@@ -0,0 +1,19 @@
+package io.jsonwebtoken.impl
+
+import org.junit.Test
+import static org.junit.Assert.*
+
+class Base64UrlCodecTest {
+
+ @Test
+ void testRemovePaddingWithEmptyByteArray() {
+
+ def codec = new Base64UrlCodec()
+
+ byte[] empty = new byte[0];
+
+ def result = codec.removePadding(empty)
+
+ assertSame empty, result
+ }
+}
diff --git a/src/test/groovy/io/jsonwebtoken/impl/DefaultJwtBuilderTest.groovy b/src/test/groovy/io/jsonwebtoken/impl/DefaultJwtBuilderTest.groovy
index 4dab7d7c..7090d03c 100644
--- a/src/test/groovy/io/jsonwebtoken/impl/DefaultJwtBuilderTest.groovy
+++ b/src/test/groovy/io/jsonwebtoken/impl/DefaultJwtBuilderTest.groovy
@@ -119,6 +119,17 @@ class DefaultJwtBuilderTest {
}
}
+ @Test
+ void testCompactWithoutPayloadOrClaims() {
+ def b = new DefaultJwtBuilder()
+ try {
+ b.compact()
+ fail()
+ } catch (IllegalStateException ise) {
+ assertEquals ise.message, "Either 'payload' or 'claims' must be specified."
+ }
+ }
+
@Test
void testCompactWithBothPayloadAndClaims() {
def b = new DefaultJwtBuilder()
@@ -197,4 +208,54 @@ class DefaultJwtBuilderTest {
}
}
+
+ @Test
+ void testSetHeaderParamsWithNullMap() {
+ def b = new DefaultJwtBuilder()
+ b.setHeaderParams(null)
+ assertNull b.header
+ }
+
+ @Test
+ void testSetHeaderParamsWithEmptyMap() {
+ def b = new DefaultJwtBuilder()
+ b.setHeaderParams([:])
+ assertNull b.header
+ }
+
+ @Test
+ void testSetIssuerWithNull() {
+ def b = new DefaultJwtBuilder()
+ b.setIssuer(null)
+ assertNull b.claims
+ }
+
+ @Test
+ void testSetSubjectWithNull() {
+ def b = new DefaultJwtBuilder()
+ b.setSubject(null)
+ assertNull b.claims
+ }
+
+ @Test
+ void testSetAudienceWithNull() {
+ def b = new DefaultJwtBuilder()
+ b.setAudience(null)
+ assertNull b.claims
+ }
+
+ @Test
+ void testSetIdWithNull() {
+ def b = new DefaultJwtBuilder()
+ b.setId(null)
+ assertNull b.claims
+ }
+
+ @Test
+ void testClaimNullValue() {
+ def b = new DefaultJwtBuilder()
+ b.claim('foo', null)
+ assertNull b.claims
+ }
+
}
From 56fb2c6a75b32433dae2b4e1e3cbf34471862438 Mon Sep 17 00:00:00 2001
From: Les Hazlewood
Date: Fri, 11 Sep 2015 11:28:43 -0700
Subject: [PATCH 06/50] Updated example to use parseClaimsJws to reflect most
common use case
---
README.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/README.md b/README.md
index d09f55e2..f0a863a8 100644
--- a/README.md
+++ b/README.md
@@ -62,7 +62,7 @@ But what if signature validation failed? You can catch `SignatureException` and
```java
try {
- Jwts.parser().setSigningKey(key).parse(compactJwt);
+ Jwts.parser().setSigningKey(key).parseClaimsJws(compactJwt);
//OK, we can trust this JWT
From 8f49666a40abc73b57ffb998f56750a6a7744c17 Mon Sep 17 00:00:00 2001
From: Micah Silverman
Date: Sat, 12 Sep 2015 01:09:44 -0400
Subject: [PATCH 07/50] Enforce arbitrary claim expectations when parsing
---
README.md | 8 +-
.../io/jsonwebtoken/ClaimJwtException.java | 3 +
.../jsonwebtoken/IncorrectClaimException.java | 32 +++++
.../jsonwebtoken/InvalidClaimException.java | 50 +++++++
src/main/java/io/jsonwebtoken/JwtParser.java | 17 +++
.../jsonwebtoken/MissingClaimException.java | 32 +++++
.../jsonwebtoken/impl/DefaultJwtParser.java | 47 +++++++
.../IncorrectClaimExceptionTest.groovy | 46 +++++++
.../InvalidClaimExceptionTest.groovy | 46 +++++++
.../io/jsonwebtoken/JwtParserTest.groovy | 125 ++++++++++++++++++
.../MissingClaimExceptionTest.groovy | 46 +++++++
11 files changed, 450 insertions(+), 2 deletions(-)
create mode 100644 src/main/java/io/jsonwebtoken/IncorrectClaimException.java
create mode 100644 src/main/java/io/jsonwebtoken/InvalidClaimException.java
create mode 100644 src/main/java/io/jsonwebtoken/MissingClaimException.java
create mode 100644 src/test/groovy/io/jsonwebtoken/IncorrectClaimExceptionTest.groovy
create mode 100644 src/test/groovy/io/jsonwebtoken/InvalidClaimExceptionTest.groovy
create mode 100644 src/test/groovy/io/jsonwebtoken/MissingClaimExceptionTest.groovy
diff --git a/README.md b/README.md
index d09f55e2..d49c82b6 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,10 @@ These feature sets will be implemented in a future release when possible. Commu
## Release Notes
+### 0.6
+
+- Added the ability to set expectations when parsing a JWT which enforces a particular claim having a particular value
+
### 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/IncorrectClaimException.java b/src/main/java/io/jsonwebtoken/IncorrectClaimException.java
new file mode 100644
index 00000000..bf63cd5a
--- /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;
+
+/**
+ * IncorrectClaimException is a subclass of the {@link InvalidClaimException} that is thrown after it is found that an
+ * expected claim has a value that is not expected.
+ *
+ * @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..9706a0b3
--- /dev/null
+++ b/src/main/java/io/jsonwebtoken/InvalidClaimException.java
@@ -0,0 +1,50 @@
+/*
+ * 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;
+
+/**
+ * InvalidClaimException is a subclass of the {@link ClaimJwtException} that is thrown after a validation of an JTW claim failed.
+ *
+ * @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 f4b190ba..c26da433 100644
--- a/src/main/java/io/jsonwebtoken/JwtParser.java
+++ b/src/main/java/io/jsonwebtoken/JwtParser.java
@@ -26,6 +26,23 @@ public interface JwtParser {
public static final char SEPARATOR_CHAR = '.';
+ /**
+ * Sets an expected value for any given claim name.
+ *
+ * If an expectation is set for a particular claim name and the JWT being parsed does not have that claim set,
+ * a {@Link MissingClaimException} will be thrown.
+ *
+ * If an expectation is set for a particular claim name and the JWT being parsed has a value that is different than
+ * the expected value, a {@link IncorrectClaimException} will be thrown.
+ *
+ * If either {@code claimName} is null or empty or {@code value} is null, the expectation is simply ignored.
+ *
+ * @param claimName
+ * @param value
+ * @return the parser for method chaining.
+ */
+ JwtParser expect(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..d453df57
--- /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;
+
+/**
+ * MissingClaimException is a subclass of the {@link InvalidClaimException} that is thrown after it is found that an
+ * expected claim is missing.
+ *
+ * @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/impl/DefaultJwtParser.java b/src/main/java/io/jsonwebtoken/impl/DefaultJwtParser.java
index ba888e04..36142292 100644
--- a/src/main/java/io/jsonwebtoken/impl/DefaultJwtParser.java
+++ b/src/main/java/io/jsonwebtoken/impl/DefaultJwtParser.java
@@ -16,11 +16,15 @@
package io.jsonwebtoken.impl;
import com.fasterxml.jackson.databind.ObjectMapper;
+import io.jsonwebtoken.ClaimJwtException;
import io.jsonwebtoken.Claims;
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;
@@ -42,6 +46,7 @@ import java.io.IOException;
import java.security.Key;
import java.text.SimpleDateFormat;
import java.util.Date;
+import java.util.LinkedHashMap;
import java.util.Map;
@SuppressWarnings("unchecked")
@@ -58,6 +63,17 @@ public class DefaultJwtParser implements JwtParser {
private SigningKeyResolver signingKeyResolver;
+ Map expectedClaims = new LinkedHashMap();
+
+ @Override
+ public JwtParser expect(String claimName, Object value) {
+ if (claimName != null && claimName.length() > 0 && value != null) {
+ expectedClaims.put(claimName, value);
+ }
+
+ return this;
+ }
+
@Override
public JwtParser setSigningKey(byte[] key) {
Assert.notEmpty(key, "signing key cannot be null or empty.");
@@ -298,6 +314,8 @@ public class DefaultJwtParser implements JwtParser {
throw new PrematureJwtException(header, claims, msg);
}
}
+
+ validateExpectedClaims(header, claims);
}
Object body = claims != null ? claims : payload;
@@ -309,6 +327,35 @@ 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);
+ 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..40c4986c 100644
--- a/src/test/groovy/io/jsonwebtoken/JwtParserTest.groovy
+++ b/src/test/groovy/io/jsonwebtoken/JwtParserTest.groovy
@@ -723,4 +723,129 @@ class JwtParserTest {
'method or, for HMAC algorithms, the resolveSigningKeyBytes(JwsHeader, String) method.'
}
}
+
+ @Test
+ void testParseExpectIgnoreNullClaimName() {
+ 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()
+
+ // expecting null claim name, but with value
+ Jwt jwt = Jwts.parser().setSigningKey(key).
+ expect(null, expectedClaimValue).
+ parseClaimsJws(compact)
+
+ assertEquals jwt.getBody().getIssuer(), 'Dummy'
+ }
+
+ @Test
+ void testParseExpectIgnoreEmptyClaimName() {
+ 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()
+
+ // expecting null claim name, but with value
+ Jwt jwt = Jwts.parser().setSigningKey(key).
+ expect("", expectedClaimValue).
+ parseClaimsJws(compact)
+
+ assertEquals jwt.getBody().getIssuer(), 'Dummy'
+ }
+
+ @Test
+ void testParseExpectIgnoreNullClaimValue() {
+ 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()
+
+ // expecting claim name, but with null value
+ Jwt jwt = Jwts.parser().setSigningKey(key).
+ expect(expectedClaimName, null).
+ parseClaimsJws(compact)
+
+ assertEquals jwt.getBody().getIssuer(), 'Dummy'
+ }
+
+ @Test
+ void testParseExpectGeneric_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).
+ expect(expectedClaimName, expectedClaimValue).
+ parseClaimsJws(compact)
+
+ assertEquals jwt.getBody().get(expectedClaimName), expectedClaimValue
+ }
+
+ @Test
+ void testParseExpectGeneric_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).
+ expect(goodClaimName, goodClaimValue).
+ parseClaimsJws(compact)
+ fail()
+ } catch (IncorrectClaimException e) {
+ assertEquals(
+ String.format(ClaimJwtException.INCORRECT_EXPECTED_CLAIM_MESSAGE_TEMPLATE, goodClaimName, goodClaimValue, badClaimValue),
+ e.getMessage()
+ )
+ }
+ }
+
+ @Test
+ void testParseExpectedGeneric_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).
+ expect(claimName, claimValue).
+ parseClaimsJws(compact)
+ fail()
+ } catch (MissingClaimException e) {
+ assertEquals(
+ String.format(ClaimJwtException.MISSING_EXPECTED_CLAIM_MESSAGE_TEMPLATE, claimName, claimValue),
+ 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
+ }
+}
From 0fab5504cdf6f179b5ab3f450afec7c5aa644eec Mon Sep 17 00:00:00 2001
From: Micah Silverman
Date: Sat, 12 Sep 2015 02:53:57 -0400
Subject: [PATCH 08/50] Added expectIssuedAt convenience method.
---
src/main/java/io/jsonwebtoken/JwtParser.java | 9 +++
.../jsonwebtoken/impl/DefaultJwtParser.java | 24 +++++-
.../io/jsonwebtoken/JwtParserTest.groovy | 73 ++++++++++++++++++-
3 files changed, 100 insertions(+), 6 deletions(-)
diff --git a/src/main/java/io/jsonwebtoken/JwtParser.java b/src/main/java/io/jsonwebtoken/JwtParser.java
index c26da433..fbf2a81f 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,14 @@ public interface JwtParser {
public static final char SEPARATOR_CHAR = '.';
+ /**
+ * Sets an expected value for the issuedAt claim.
+ *
+ * @param issuedAt
+ * @return the parser for method chaining.
+ */
+ JwtParser expectIssuedAt(Date issuedAt);
+
/**
* Sets an expected value for any given claim name.
*
diff --git a/src/main/java/io/jsonwebtoken/impl/DefaultJwtParser.java b/src/main/java/io/jsonwebtoken/impl/DefaultJwtParser.java
index 36142292..26f17f0e 100644
--- a/src/main/java/io/jsonwebtoken/impl/DefaultJwtParser.java
+++ b/src/main/java/io/jsonwebtoken/impl/DefaultJwtParser.java
@@ -46,7 +46,6 @@ import java.io.IOException;
import java.security.Key;
import java.text.SimpleDateFormat;
import java.util.Date;
-import java.util.LinkedHashMap;
import java.util.Map;
@SuppressWarnings("unchecked")
@@ -63,7 +62,16 @@ public class DefaultJwtParser implements JwtParser {
private SigningKeyResolver signingKeyResolver;
- Map expectedClaims = new LinkedHashMap();
+ Claims expectedClaims = new DefaultClaims();
+
+ @Override
+ public JwtParser expectIssuedAt(Date issuedAt) {
+ if (issuedAt != null) {
+ expectedClaims.setIssuedAt(issuedAt);
+ }
+
+ return this;
+ }
@Override
public JwtParser expect(String claimName, Object value) {
@@ -329,8 +337,16 @@ 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);
+ Object expectedClaimValue = null;
+ Object actualClaimValue = null;
+ if (Claims.ISSUED_AT.equals(expectedClaimName)) {
+ expectedClaimValue = expectedClaims.getIssuedAt();
+ actualClaimValue = claims.getIssuedAt();
+ } else {
+ expectedClaimValue = expectedClaims.get(expectedClaimName);
+ actualClaimValue = claims.get(expectedClaimName);
+ }
+
InvalidClaimException invalidClaimException = null;
if (actualClaimValue == null) {
diff --git a/src/test/groovy/io/jsonwebtoken/JwtParserTest.groovy b/src/test/groovy/io/jsonwebtoken/JwtParserTest.groovy
index 40c4986c..c5726dab 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 {
@@ -819,7 +821,7 @@ class JwtParserTest {
fail()
} catch (IncorrectClaimException e) {
assertEquals(
- String.format(ClaimJwtException.INCORRECT_EXPECTED_CLAIM_MESSAGE_TEMPLATE, goodClaimName, goodClaimValue, badClaimValue),
+ String.format(INCORRECT_EXPECTED_CLAIM_MESSAGE_TEMPLATE, goodClaimName, goodClaimValue, badClaimValue),
e.getMessage()
)
}
@@ -843,7 +845,74 @@ class JwtParserTest {
fail()
} catch (MissingClaimException e) {
assertEquals(
- String.format(ClaimJwtException.MISSING_EXPECTED_CLAIM_MESSAGE_TEMPLATE, claimName, claimValue),
+ String.format(MISSING_EXPECTED_CLAIM_MESSAGE_TEMPLATE, claimName, claimValue),
+ e.getMessage()
+ )
+ }
+ }
+
+ @Test
+ void testParseExpectIssuedAt_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).
+ expectIssuedAt(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 testParseExpectIssuedAt_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).
+ expectIssuedAt(goodIssuedAt).
+ parseClaimsJws(compact)
+ fail()
+ } catch(IncorrectClaimException e) {
+ assertEquals(
+ String.format(INCORRECT_EXPECTED_CLAIM_MESSAGE_TEMPLATE, Claims.ISSUED_AT, goodIssuedAt, badIssuedAt),
+ e.getMessage()
+ )
+ }
+ }
+
+ @Test
+ void testParseExpectIssuedAt_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).
+ expectIssuedAt(issuedAt).
+ parseClaimsJws(compact)
+ fail()
+ } catch(MissingClaimException e) {
+ assertEquals(
+ String.format(MISSING_EXPECTED_CLAIM_MESSAGE_TEMPLATE, Claims.ISSUED_AT, issuedAt),
e.getMessage()
)
}
From 056dc819e2cc44deb8925fe534b2a23d20e3db4c Mon Sep 17 00:00:00 2001
From: Micah Silverman
Date: Sat, 12 Sep 2015 03:08:03 -0400
Subject: [PATCH 09/50] Added expectIssuer convenience method.
---
src/main/java/io/jsonwebtoken/JwtParser.java | 8 +++
.../jsonwebtoken/impl/DefaultJwtParser.java | 15 ++++-
.../io/jsonwebtoken/JwtParserTest.groovy | 64 +++++++++++++++++++
3 files changed, 85 insertions(+), 2 deletions(-)
diff --git a/src/main/java/io/jsonwebtoken/JwtParser.java b/src/main/java/io/jsonwebtoken/JwtParser.java
index fbf2a81f..f9c2e54e 100644
--- a/src/main/java/io/jsonwebtoken/JwtParser.java
+++ b/src/main/java/io/jsonwebtoken/JwtParser.java
@@ -27,6 +27,14 @@ public interface JwtParser {
public static final char SEPARATOR_CHAR = '.';
+ /**
+ * Sets an expected value for the issuer claim.
+ *
+ * @param issuer
+ * @return the parser for method chaining.
+ */
+ JwtParser expectIssuer(String issuer);
+
/**
* Sets an expected value for the issuedAt claim.
*
diff --git a/src/main/java/io/jsonwebtoken/impl/DefaultJwtParser.java b/src/main/java/io/jsonwebtoken/impl/DefaultJwtParser.java
index 26f17f0e..be898304 100644
--- a/src/main/java/io/jsonwebtoken/impl/DefaultJwtParser.java
+++ b/src/main/java/io/jsonwebtoken/impl/DefaultJwtParser.java
@@ -73,6 +73,13 @@ public class DefaultJwtParser implements JwtParser {
return this;
}
+ @Override
+ public JwtParser expectIssuer(String issuer) {
+ expect(Claims.ISSUER, issuer);
+
+ return this;
+ }
+
@Override
public JwtParser expect(String claimName, Object value) {
if (claimName != null && claimName.length() > 0 && value != null) {
@@ -337,8 +344,12 @@ public class DefaultJwtParser implements JwtParser {
private void validateExpectedClaims(Header header, Claims claims) {
for (String expectedClaimName : expectedClaims.keySet()) {
- Object expectedClaimValue = null;
- Object actualClaimValue = null;
+ Object expectedClaimValue;
+ Object actualClaimValue;
+
+ // since issued at is a date, call the specific method
+ // other methods deal with strings and the more
+ // general method can be used
if (Claims.ISSUED_AT.equals(expectedClaimName)) {
expectedClaimValue = expectedClaims.getIssuedAt();
actualClaimValue = claims.getIssuedAt();
diff --git a/src/test/groovy/io/jsonwebtoken/JwtParserTest.groovy b/src/test/groovy/io/jsonwebtoken/JwtParserTest.groovy
index c5726dab..da3dbb21 100644
--- a/src/test/groovy/io/jsonwebtoken/JwtParserTest.groovy
+++ b/src/test/groovy/io/jsonwebtoken/JwtParserTest.groovy
@@ -917,4 +917,68 @@ class JwtParserTest {
)
}
}
+
+ @Test
+ void testParseExpectIssuer_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).
+ expectIssuer(issuer).
+ parseClaimsJws(compact)
+
+ assertEquals jwt.getBody().getIssuer(), issuer
+ }
+
+ @Test
+ void testParseExpectIssuer_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).
+ expectIssuer(goodIssuer).
+ parseClaimsJws(compact)
+ fail()
+ } catch(IncorrectClaimException e) {
+ assertEquals(
+ String.format(INCORRECT_EXPECTED_CLAIM_MESSAGE_TEMPLATE, Claims.ISSUER, goodIssuer, badIssuer),
+ e.getMessage()
+ )
+ }
+ }
+
+ @Test
+ void testParseExpectIssuer_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).
+ expectIssuer(issuer).
+ parseClaimsJws(compact)
+ fail()
+ } catch(MissingClaimException e) {
+ assertEquals(
+ String.format(MISSING_EXPECTED_CLAIM_MESSAGE_TEMPLATE, Claims.ISSUER, issuer),
+ e.getMessage()
+ )
+ }
+ }
}
From fd04a357cb4e0ae90d991c94924335032bc9dbe5 Mon Sep 17 00:00:00 2001
From: Micah Silverman
Date: Sat, 12 Sep 2015 03:32:56 -0400
Subject: [PATCH 10/50] Added expectAudience convenience method.
---
src/main/java/io/jsonwebtoken/JwtParser.java | 8 +++
.../jsonwebtoken/impl/DefaultJwtParser.java | 7 ++
.../io/jsonwebtoken/JwtParserTest.groovy | 65 +++++++++++++++++++
3 files changed, 80 insertions(+)
diff --git a/src/main/java/io/jsonwebtoken/JwtParser.java b/src/main/java/io/jsonwebtoken/JwtParser.java
index f9c2e54e..9f6906c7 100644
--- a/src/main/java/io/jsonwebtoken/JwtParser.java
+++ b/src/main/java/io/jsonwebtoken/JwtParser.java
@@ -27,6 +27,14 @@ public interface JwtParser {
public static final char SEPARATOR_CHAR = '.';
+ /**
+ * Sets an expected value for the audience claim.
+ *
+ * @param audience
+ * @return the parser for method chaining.
+ */
+ JwtParser expectAudience(String audience);
+
/**
* Sets an expected value for the issuer claim.
*
diff --git a/src/main/java/io/jsonwebtoken/impl/DefaultJwtParser.java b/src/main/java/io/jsonwebtoken/impl/DefaultJwtParser.java
index be898304..08664d09 100644
--- a/src/main/java/io/jsonwebtoken/impl/DefaultJwtParser.java
+++ b/src/main/java/io/jsonwebtoken/impl/DefaultJwtParser.java
@@ -80,6 +80,13 @@ public class DefaultJwtParser implements JwtParser {
return this;
}
+ @Override
+ public JwtParser expectAudience(String audience) {
+ expect(Claims.AUDIENCE, audience);
+
+ return this;
+ }
+
@Override
public JwtParser expect(String claimName, Object value) {
if (claimName != null && claimName.length() > 0 && value != null) {
diff --git a/src/test/groovy/io/jsonwebtoken/JwtParserTest.groovy b/src/test/groovy/io/jsonwebtoken/JwtParserTest.groovy
index da3dbb21..7cd1d88d 100644
--- a/src/test/groovy/io/jsonwebtoken/JwtParserTest.groovy
+++ b/src/test/groovy/io/jsonwebtoken/JwtParserTest.groovy
@@ -981,4 +981,69 @@ class JwtParserTest {
)
}
}
+
+ @Test
+ void testParseExpectAudience_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).
+ expectAudience(audience).
+ parseClaimsJws(compact)
+
+ assertEquals jwt.getBody().getAudience(), audience
+ }
+
+ @Test
+ void testParseExpectAudience_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).
+ expectAudience(goodAudience).
+ parseClaimsJws(compact)
+ fail()
+ } catch(IncorrectClaimException e) {
+ assertEquals(
+ String.format(INCORRECT_EXPECTED_CLAIM_MESSAGE_TEMPLATE, Claims.AUDIENCE, goodAudience, badAudience),
+ e.getMessage()
+ )
+ }
+ }
+
+ @Test
+ void testParseExpectAudience_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).
+ expectAudience(audience).
+ parseClaimsJws(compact)
+ fail()
+ } catch(MissingClaimException e) {
+ assertEquals(
+ String.format(MISSING_EXPECTED_CLAIM_MESSAGE_TEMPLATE, Claims.AUDIENCE, audience),
+ e.getMessage()
+ )
+ }
+ }
+
}
From f3c8f10f32d89a447ead2311536a5de90b5a02eb Mon Sep 17 00:00:00 2001
From: Micah Silverman
Date: Sat, 12 Sep 2015 03:40:20 -0400
Subject: [PATCH 11/50] Added expectSubject convenience method.
---
src/main/java/io/jsonwebtoken/JwtParser.java | 8 +++
.../jsonwebtoken/impl/DefaultJwtParser.java | 7 +++
.../io/jsonwebtoken/JwtParserTest.groovy | 63 +++++++++++++++++++
3 files changed, 78 insertions(+)
diff --git a/src/main/java/io/jsonwebtoken/JwtParser.java b/src/main/java/io/jsonwebtoken/JwtParser.java
index 9f6906c7..c74ec9e6 100644
--- a/src/main/java/io/jsonwebtoken/JwtParser.java
+++ b/src/main/java/io/jsonwebtoken/JwtParser.java
@@ -27,6 +27,14 @@ public interface JwtParser {
public static final char SEPARATOR_CHAR = '.';
+ /**
+ * Sets an expected value for the subject claim.
+ *
+ * @param subject
+ * @return the parser for method chaining.
+ */
+ JwtParser expectSubject(String subject);
+
/**
* Sets an expected value for the audience claim.
*
diff --git a/src/main/java/io/jsonwebtoken/impl/DefaultJwtParser.java b/src/main/java/io/jsonwebtoken/impl/DefaultJwtParser.java
index 08664d09..a251ca8e 100644
--- a/src/main/java/io/jsonwebtoken/impl/DefaultJwtParser.java
+++ b/src/main/java/io/jsonwebtoken/impl/DefaultJwtParser.java
@@ -87,6 +87,13 @@ public class DefaultJwtParser implements JwtParser {
return this;
}
+ @Override
+ public JwtParser expectSubject(String subject) {
+ expect(Claims.SUBJECT, subject);
+
+ return this;
+ }
+
@Override
public JwtParser expect(String claimName, Object value) {
if (claimName != null && claimName.length() > 0 && value != null) {
diff --git a/src/test/groovy/io/jsonwebtoken/JwtParserTest.groovy b/src/test/groovy/io/jsonwebtoken/JwtParserTest.groovy
index 7cd1d88d..c6116842 100644
--- a/src/test/groovy/io/jsonwebtoken/JwtParserTest.groovy
+++ b/src/test/groovy/io/jsonwebtoken/JwtParserTest.groovy
@@ -1046,4 +1046,67 @@ class JwtParserTest {
}
}
+ @Test
+ void testParseExpectSubject_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).
+ expectSubject(subject).
+ parseClaimsJws(compact)
+
+ assertEquals jwt.getBody().getSubject(), subject
+ }
+
+ @Test
+ void testParseExpectSubject_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).
+ expectSubject(goodSubject).
+ parseClaimsJws(compact)
+ fail()
+ } catch(IncorrectClaimException e) {
+ assertEquals(
+ String.format(INCORRECT_EXPECTED_CLAIM_MESSAGE_TEMPLATE, Claims.SUBJECT, goodSubject, badSubject),
+ e.getMessage()
+ )
+ }
+ }
+
+ @Test
+ void testParseExpectSubject_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).
+ expectSubject(subject).
+ parseClaimsJws(compact)
+ fail()
+ } catch(MissingClaimException e) {
+ assertEquals(
+ String.format(MISSING_EXPECTED_CLAIM_MESSAGE_TEMPLATE, Claims.SUBJECT, subject),
+ e.getMessage()
+ )
+ }
+ }
}
From f2e620e36bff02804f99a20b5190cafd42bb22b3 Mon Sep 17 00:00:00 2001
From: Micah Silverman
Date: Sat, 12 Sep 2015 03:51:00 -0400
Subject: [PATCH 12/50] Added expectId convenience method.
---
src/main/java/io/jsonwebtoken/JwtParser.java | 8 +++
.../jsonwebtoken/impl/DefaultJwtParser.java | 7 ++
.../io/jsonwebtoken/JwtParserTest.groovy | 64 +++++++++++++++++++
3 files changed, 79 insertions(+)
diff --git a/src/main/java/io/jsonwebtoken/JwtParser.java b/src/main/java/io/jsonwebtoken/JwtParser.java
index c74ec9e6..3e0cff47 100644
--- a/src/main/java/io/jsonwebtoken/JwtParser.java
+++ b/src/main/java/io/jsonwebtoken/JwtParser.java
@@ -27,6 +27,14 @@ public interface JwtParser {
public static final char SEPARATOR_CHAR = '.';
+ /**
+ * Sets an expected value for the jti claim.
+ *
+ * @param id
+ * @return the parser for method chaining.
+ */
+ JwtParser expectId(String id);
+
/**
* Sets an expected value for the subject claim.
*
diff --git a/src/main/java/io/jsonwebtoken/impl/DefaultJwtParser.java b/src/main/java/io/jsonwebtoken/impl/DefaultJwtParser.java
index a251ca8e..30588e1f 100644
--- a/src/main/java/io/jsonwebtoken/impl/DefaultJwtParser.java
+++ b/src/main/java/io/jsonwebtoken/impl/DefaultJwtParser.java
@@ -94,6 +94,13 @@ public class DefaultJwtParser implements JwtParser {
return this;
}
+ @Override
+ public JwtParser expectId(String id) {
+ expect(Claims.ID, id);
+
+ return this;
+ }
+
@Override
public JwtParser expect(String claimName, Object value) {
if (claimName != null && claimName.length() > 0 && value != null) {
diff --git a/src/test/groovy/io/jsonwebtoken/JwtParserTest.groovy b/src/test/groovy/io/jsonwebtoken/JwtParserTest.groovy
index c6116842..a5b74580 100644
--- a/src/test/groovy/io/jsonwebtoken/JwtParserTest.groovy
+++ b/src/test/groovy/io/jsonwebtoken/JwtParserTest.groovy
@@ -1109,4 +1109,68 @@ class JwtParserTest {
)
}
}
+
+ @Test
+ void testParseExpectId_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).
+ expectId(id).
+ parseClaimsJws(compact)
+
+ assertEquals jwt.getBody().getId(), id
+ }
+
+ @Test
+ void testParseExpectId_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).
+ expectId(goodId).
+ parseClaimsJws(compact)
+ fail()
+ } catch(IncorrectClaimException e) {
+ assertEquals(
+ String.format(INCORRECT_EXPECTED_CLAIM_MESSAGE_TEMPLATE, Claims.ID, goodId, badId),
+ e.getMessage()
+ )
+ }
+ }
+
+ @Test
+ void testParseExpectId_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).
+ expectId(id).
+ parseClaimsJws(compact)
+ fail()
+ } catch(MissingClaimException e) {
+ assertEquals(
+ String.format(MISSING_EXPECTED_CLAIM_MESSAGE_TEMPLATE, Claims.ID, id),
+ e.getMessage()
+ )
+ }
+ }
}
From 5ecaacde5a61b8f1c1f7ad711ab51720bb095aa6 Mon Sep 17 00:00:00 2001
From: Micah Silverman
Date: Wed, 23 Sep 2015 01:12:54 -0400
Subject: [PATCH 13/50] Don't allow empty or null claimName or null value for
claim expectations.
---
.../jsonwebtoken/impl/DefaultJwtParser.java | 6 +-
.../io/jsonwebtoken/JwtParserTest.groovy | 60 ++++++++++++-------
2 files changed, 42 insertions(+), 24 deletions(-)
diff --git a/src/main/java/io/jsonwebtoken/impl/DefaultJwtParser.java b/src/main/java/io/jsonwebtoken/impl/DefaultJwtParser.java
index 30588e1f..9e9c5ee9 100644
--- a/src/main/java/io/jsonwebtoken/impl/DefaultJwtParser.java
+++ b/src/main/java/io/jsonwebtoken/impl/DefaultJwtParser.java
@@ -103,9 +103,9 @@ public class DefaultJwtParser implements JwtParser {
@Override
public JwtParser expect(String claimName, Object value) {
- if (claimName != null && claimName.length() > 0 && value != null) {
- expectedClaims.put(claimName, 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;
}
diff --git a/src/test/groovy/io/jsonwebtoken/JwtParserTest.groovy b/src/test/groovy/io/jsonwebtoken/JwtParserTest.groovy
index a5b74580..ea0bda6a 100644
--- a/src/test/groovy/io/jsonwebtoken/JwtParserTest.groovy
+++ b/src/test/groovy/io/jsonwebtoken/JwtParserTest.groovy
@@ -727,7 +727,7 @@ class JwtParserTest {
}
@Test
- void testParseExpectIgnoreNullClaimName() {
+ void testParseExpectDontAllowNullClaimName() {
def expectedClaimValue = 'A Most Awesome Claim Value'
byte[] key = randomKey()
@@ -737,35 +737,47 @@ class JwtParserTest {
setIssuer('Dummy').
compact()
- // expecting null claim name, but with value
- Jwt jwt = Jwts.parser().setSigningKey(key).
- expect(null, expectedClaimValue).
- parseClaimsJws(compact)
-
- assertEquals jwt.getBody().getIssuer(), 'Dummy'
+ try {
+ // expecting null claim name, but with value
+ Jwt jwt = Jwts.parser().setSigningKey(key).
+ expect(null, expectedClaimValue).
+ parseClaimsJws(compact)
+ fail()
+ } catch (IllegalArgumentException e) {
+ assertEquals(
+ "claim name cannot be null or empty.",
+ e.getMessage()
+ )
+ }
}
@Test
- void testParseExpectIgnoreEmptyClaimName() {
+ void testParseExpectDontAllowEmptyClaimName() {
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()
+ setIssuer('Dummy').
+ compact()
- // expecting null claim name, but with value
- Jwt jwt = Jwts.parser().setSigningKey(key).
+ try {
+ // expecting null claim name, but with value
+ Jwt jwt = Jwts.parser().setSigningKey(key).
expect("", expectedClaimValue).
parseClaimsJws(compact)
-
- assertEquals jwt.getBody().getIssuer(), 'Dummy'
+ fail()
+ } catch (IllegalArgumentException e) {
+ assertEquals(
+ "claim name cannot be null or empty.",
+ e.getMessage()
+ )
+ }
}
@Test
- void testParseExpectIgnoreNullClaimValue() {
+ void testParseExpectDontAllowNullClaimValue() {
def expectedClaimName = 'A Most Awesome Claim Name'
byte[] key = randomKey()
@@ -775,12 +787,18 @@ class JwtParserTest {
setIssuer('Dummy').
compact()
- // expecting claim name, but with null value
- Jwt jwt = Jwts.parser().setSigningKey(key).
- expect(expectedClaimName, null).
- parseClaimsJws(compact)
-
- assertEquals jwt.getBody().getIssuer(), 'Dummy'
+ try {
+ // expecting claim name, but with null value
+ Jwt jwt = Jwts.parser().setSigningKey(key).
+ expect(expectedClaimName, null).
+ parseClaimsJws(compact)
+ fail()
+ } catch (IllegalArgumentException e) {
+ assertEquals(
+ "The value cannot be null for claim name: " + expectedClaimName,
+ e.getMessage()
+ )
+ }
}
@Test
From 62ccd16748f3c89b41dfdd62b8978c5ade772e29 Mon Sep 17 00:00:00 2001
From: Micah Silverman
Date: Wed, 23 Sep 2015 01:31:17 -0400
Subject: [PATCH 14/50] Call underlying delegate methods for expected claims.
---
.../java/io/jsonwebtoken/impl/DefaultJwtParser.java | 12 +++++-------
1 file changed, 5 insertions(+), 7 deletions(-)
diff --git a/src/main/java/io/jsonwebtoken/impl/DefaultJwtParser.java b/src/main/java/io/jsonwebtoken/impl/DefaultJwtParser.java
index 9e9c5ee9..228a40c0 100644
--- a/src/main/java/io/jsonwebtoken/impl/DefaultJwtParser.java
+++ b/src/main/java/io/jsonwebtoken/impl/DefaultJwtParser.java
@@ -66,37 +66,35 @@ public class DefaultJwtParser implements JwtParser {
@Override
public JwtParser expectIssuedAt(Date issuedAt) {
- if (issuedAt != null) {
- expectedClaims.setIssuedAt(issuedAt);
- }
+ expectedClaims.setIssuedAt(issuedAt);
return this;
}
@Override
public JwtParser expectIssuer(String issuer) {
- expect(Claims.ISSUER, issuer);
+ expectedClaims.setIssuer(issuer);
return this;
}
@Override
public JwtParser expectAudience(String audience) {
- expect(Claims.AUDIENCE, audience);
+ expectedClaims.setAudience(audience);
return this;
}
@Override
public JwtParser expectSubject(String subject) {
- expect(Claims.SUBJECT, subject);
+ expectedClaims.setSubject(subject);
return this;
}
@Override
public JwtParser expectId(String id) {
- expect(Claims.ID, id);
+ expectedClaims.setId(id);
return this;
}
From 5dd95b67553123a8d8d4c62b9137604078675599 Mon Sep 17 00:00:00 2001
From: Micah Silverman
Date: Wed, 23 Sep 2015 03:18:10 -0400
Subject: [PATCH 15/50] Refactored validateExpectedClaims
---
.../jsonwebtoken/impl/DefaultJwtParser.java | 33 +++++++++++++------
1 file changed, 23 insertions(+), 10 deletions(-)
diff --git a/src/main/java/io/jsonwebtoken/impl/DefaultJwtParser.java b/src/main/java/io/jsonwebtoken/impl/DefaultJwtParser.java
index 228a40c0..5a815c76 100644
--- a/src/main/java/io/jsonwebtoken/impl/DefaultJwtParser.java
+++ b/src/main/java/io/jsonwebtoken/impl/DefaultJwtParser.java
@@ -363,18 +363,32 @@ public class DefaultJwtParser implements JwtParser {
private void validateExpectedClaims(Header header, Claims claims) {
for (String expectedClaimName : expectedClaims.keySet()) {
- Object expectedClaimValue;
- Object actualClaimValue;
- // since issued at is a date, call the specific method
- // other methods deal with strings and the more
- // general method can be used
+ // this will be overridden if one of the default claims is used
+ Object expectedClaimValue = expectedClaims.get(expectedClaimName);
+ Object actualClaimValue = claims.get(expectedClaimName);
+
if (Claims.ISSUED_AT.equals(expectedClaimName)) {
expectedClaimValue = expectedClaims.getIssuedAt();
actualClaimValue = claims.getIssuedAt();
- } else {
- expectedClaimValue = expectedClaims.get(expectedClaimName);
- actualClaimValue = claims.get(expectedClaimName);
+ } else if (Claims.AUDIENCE.equals(expectedClaimName)) {
+ expectedClaimValue = expectedClaims.getAudience();
+ actualClaimValue = claims.getAudience();
+ } else if (Claims.ISSUER.equals(expectedClaimName)) {
+ expectedClaimValue = expectedClaims.getIssuer();
+ actualClaimValue = claims.getIssuer();
+ } else if (Claims.SUBJECT.equals(expectedClaimName)) {
+ expectedClaimValue = expectedClaims.getSubject();
+ actualClaimValue = claims.getSubject();
+ } else if (Claims.EXPIRATION.equals(expectedClaimName)) {
+ expectedClaimValue = expectedClaims.getExpiration();
+ actualClaimValue = claims.getExpiration();
+ } else if (Claims.NOT_BEFORE.equals(expectedClaimName)) {
+ expectedClaimValue = expectedClaims.getNotBefore();
+ actualClaimValue = claims.getNotBefore();
+ } else if (Claims.ID.equals(expectedClaimName)) {
+ expectedClaimValue = expectedClaims.getId();
+ actualClaimValue = claims.getId();
}
InvalidClaimException invalidClaimException = null;
@@ -385,8 +399,7 @@ public class DefaultJwtParser implements JwtParser {
expectedClaimName, expectedClaimValue
);
invalidClaimException = new MissingClaimException(header, claims, msg);
- }
- else if (!expectedClaimValue.equals(actualClaimValue)) {
+ } else if (!expectedClaimValue.equals(actualClaimValue)) {
String msg = String.format(
ClaimJwtException.INCORRECT_EXPECTED_CLAIM_MESSAGE_TEMPLATE,
expectedClaimName, expectedClaimValue, actualClaimValue
From 72acd649c7092d3d2d2d23c4cb3306bf8a9bd902 Mon Sep 17 00:00:00 2001
From: Micah Silverman
Date: Wed, 23 Sep 2015 03:25:29 -0400
Subject: [PATCH 16/50] Updated custom Exception javadocs.
---
src/main/java/io/jsonwebtoken/IncorrectClaimException.java | 4 ++--
src/main/java/io/jsonwebtoken/InvalidClaimException.java | 6 +++++-
src/main/java/io/jsonwebtoken/MissingClaimException.java | 4 ++--
3 files changed, 9 insertions(+), 5 deletions(-)
diff --git a/src/main/java/io/jsonwebtoken/IncorrectClaimException.java b/src/main/java/io/jsonwebtoken/IncorrectClaimException.java
index bf63cd5a..860a4d84 100644
--- a/src/main/java/io/jsonwebtoken/IncorrectClaimException.java
+++ b/src/main/java/io/jsonwebtoken/IncorrectClaimException.java
@@ -16,8 +16,8 @@
package io.jsonwebtoken;
/**
- * IncorrectClaimException is a subclass of the {@link InvalidClaimException} that is thrown after it is found that an
- * expected claim has a value that is not expected.
+ * 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
*/
diff --git a/src/main/java/io/jsonwebtoken/InvalidClaimException.java b/src/main/java/io/jsonwebtoken/InvalidClaimException.java
index 9706a0b3..8880792c 100644
--- a/src/main/java/io/jsonwebtoken/InvalidClaimException.java
+++ b/src/main/java/io/jsonwebtoken/InvalidClaimException.java
@@ -16,7 +16,11 @@
package io.jsonwebtoken;
/**
- * InvalidClaimException is a subclass of the {@link ClaimJwtException} that is thrown after a validation of an JTW claim failed.
+ * 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
*/
diff --git a/src/main/java/io/jsonwebtoken/MissingClaimException.java b/src/main/java/io/jsonwebtoken/MissingClaimException.java
index d453df57..030fe98d 100644
--- a/src/main/java/io/jsonwebtoken/MissingClaimException.java
+++ b/src/main/java/io/jsonwebtoken/MissingClaimException.java
@@ -16,8 +16,8 @@
package io.jsonwebtoken;
/**
- * MissingClaimException is a subclass of the {@link InvalidClaimException} that is thrown after it is found that an
- * expected claim is missing.
+ * Exception thrown when discovering that a required claim is not present, indicating the JWT is
+ * invalid and may not be used.
*
* @since 0.6
*/
From ddda2f92d3d5e36c1b5469eea553d89e7b6e6de0 Mon Sep 17 00:00:00 2001
From: Micah Silverman
Date: Wed, 23 Sep 2015 03:35:41 -0400
Subject: [PATCH 17/50] Renamed all the expect methods to require.
---
src/main/java/io/jsonwebtoken/JwtParser.java | 12 +--
.../jsonwebtoken/impl/DefaultJwtParser.java | 12 +--
.../io/jsonwebtoken/JwtParserTest.groovy | 84 +++++++++----------
3 files changed, 54 insertions(+), 54 deletions(-)
diff --git a/src/main/java/io/jsonwebtoken/JwtParser.java b/src/main/java/io/jsonwebtoken/JwtParser.java
index 3e0cff47..1ed015ff 100644
--- a/src/main/java/io/jsonwebtoken/JwtParser.java
+++ b/src/main/java/io/jsonwebtoken/JwtParser.java
@@ -33,7 +33,7 @@ public interface JwtParser {
* @param id
* @return the parser for method chaining.
*/
- JwtParser expectId(String id);
+ JwtParser requireId(String id);
/**
* Sets an expected value for the subject claim.
@@ -41,7 +41,7 @@ public interface JwtParser {
* @param subject
* @return the parser for method chaining.
*/
- JwtParser expectSubject(String subject);
+ JwtParser requireSubject(String subject);
/**
* Sets an expected value for the audience claim.
@@ -49,7 +49,7 @@ public interface JwtParser {
* @param audience
* @return the parser for method chaining.
*/
- JwtParser expectAudience(String audience);
+ JwtParser requireAudience(String audience);
/**
* Sets an expected value for the issuer claim.
@@ -57,7 +57,7 @@ public interface JwtParser {
* @param issuer
* @return the parser for method chaining.
*/
- JwtParser expectIssuer(String issuer);
+ JwtParser requireIssuer(String issuer);
/**
* Sets an expected value for the issuedAt claim.
@@ -65,7 +65,7 @@ public interface JwtParser {
* @param issuedAt
* @return the parser for method chaining.
*/
- JwtParser expectIssuedAt(Date issuedAt);
+ JwtParser requireIssuedAt(Date issuedAt);
/**
* Sets an expected value for any given claim name.
@@ -82,7 +82,7 @@ public interface JwtParser {
* @param value
* @return the parser for method chaining.
*/
- JwtParser expect(String claimName, Object value);
+ 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
diff --git a/src/main/java/io/jsonwebtoken/impl/DefaultJwtParser.java b/src/main/java/io/jsonwebtoken/impl/DefaultJwtParser.java
index 5a815c76..a52e7b54 100644
--- a/src/main/java/io/jsonwebtoken/impl/DefaultJwtParser.java
+++ b/src/main/java/io/jsonwebtoken/impl/DefaultJwtParser.java
@@ -65,42 +65,42 @@ public class DefaultJwtParser implements JwtParser {
Claims expectedClaims = new DefaultClaims();
@Override
- public JwtParser expectIssuedAt(Date issuedAt) {
+ public JwtParser requireIssuedAt(Date issuedAt) {
expectedClaims.setIssuedAt(issuedAt);
return this;
}
@Override
- public JwtParser expectIssuer(String issuer) {
+ public JwtParser requireIssuer(String issuer) {
expectedClaims.setIssuer(issuer);
return this;
}
@Override
- public JwtParser expectAudience(String audience) {
+ public JwtParser requireAudience(String audience) {
expectedClaims.setAudience(audience);
return this;
}
@Override
- public JwtParser expectSubject(String subject) {
+ public JwtParser requireSubject(String subject) {
expectedClaims.setSubject(subject);
return this;
}
@Override
- public JwtParser expectId(String id) {
+ public JwtParser requireId(String id) {
expectedClaims.setId(id);
return this;
}
@Override
- public JwtParser expect(String claimName, Object value) {
+ 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);
diff --git a/src/test/groovy/io/jsonwebtoken/JwtParserTest.groovy b/src/test/groovy/io/jsonwebtoken/JwtParserTest.groovy
index ea0bda6a..4fbd36ca 100644
--- a/src/test/groovy/io/jsonwebtoken/JwtParserTest.groovy
+++ b/src/test/groovy/io/jsonwebtoken/JwtParserTest.groovy
@@ -727,7 +727,7 @@ class JwtParserTest {
}
@Test
- void testParseExpectDontAllowNullClaimName() {
+ void testParseRequireDontAllowNullClaimName() {
def expectedClaimValue = 'A Most Awesome Claim Value'
byte[] key = randomKey()
@@ -740,7 +740,7 @@ class JwtParserTest {
try {
// expecting null claim name, but with value
Jwt jwt = Jwts.parser().setSigningKey(key).
- expect(null, expectedClaimValue).
+ require(null, expectedClaimValue).
parseClaimsJws(compact)
fail()
} catch (IllegalArgumentException e) {
@@ -752,7 +752,7 @@ class JwtParserTest {
}
@Test
- void testParseExpectDontAllowEmptyClaimName() {
+ void testParseRequireDontAllowEmptyClaimName() {
def expectedClaimValue = 'A Most Awesome Claim Value'
byte[] key = randomKey()
@@ -765,7 +765,7 @@ class JwtParserTest {
try {
// expecting null claim name, but with value
Jwt jwt = Jwts.parser().setSigningKey(key).
- expect("", expectedClaimValue).
+ require("", expectedClaimValue).
parseClaimsJws(compact)
fail()
} catch (IllegalArgumentException e) {
@@ -777,7 +777,7 @@ class JwtParserTest {
}
@Test
- void testParseExpectDontAllowNullClaimValue() {
+ void testParseRequireDontAllowNullClaimValue() {
def expectedClaimName = 'A Most Awesome Claim Name'
byte[] key = randomKey()
@@ -790,7 +790,7 @@ class JwtParserTest {
try {
// expecting claim name, but with null value
Jwt jwt = Jwts.parser().setSigningKey(key).
- expect(expectedClaimName, null).
+ require(expectedClaimName, null).
parseClaimsJws(compact)
fail()
} catch (IllegalArgumentException e) {
@@ -802,7 +802,7 @@ class JwtParserTest {
}
@Test
- void testParseExpectGeneric_Success() {
+ void testParseRequireGeneric_Success() {
def expectedClaimName = 'A Most Awesome Claim Name'
def expectedClaimValue = 'A Most Awesome Claim Value'
@@ -813,14 +813,14 @@ class JwtParserTest {
compact()
Jwt jwt = Jwts.parser().setSigningKey(key).
- expect(expectedClaimName, expectedClaimValue).
+ require(expectedClaimName, expectedClaimValue).
parseClaimsJws(compact)
assertEquals jwt.getBody().get(expectedClaimName), expectedClaimValue
}
@Test
- void testParseExpectGeneric_Incorrect_Fail() {
+ void testParseRequireGeneric_Incorrect_Fail() {
def goodClaimName = 'A Most Awesome Claim Name'
def goodClaimValue = 'A Most Awesome Claim Value'
@@ -834,7 +834,7 @@ class JwtParserTest {
try {
Jwts.parser().setSigningKey(key).
- expect(goodClaimName, goodClaimValue).
+ require(goodClaimName, goodClaimValue).
parseClaimsJws(compact)
fail()
} catch (IncorrectClaimException e) {
@@ -846,7 +846,7 @@ class JwtParserTest {
}
@Test
- void testParseExpectedGeneric_Missing_Fail() {
+ void testParseRequireedGeneric_Missing_Fail() {
def claimName = 'A Most Awesome Claim Name'
def claimValue = 'A Most Awesome Claim Value'
@@ -858,7 +858,7 @@ class JwtParserTest {
try {
Jwt jwt = Jwts.parser().setSigningKey(key).
- expect(claimName, claimValue).
+ require(claimName, claimValue).
parseClaimsJws(compact)
fail()
} catch (MissingClaimException e) {
@@ -870,7 +870,7 @@ class JwtParserTest {
}
@Test
- void testParseExpectIssuedAt_Success() {
+ void testParseRequireIssuedAt_Success() {
def issuedAt = new Date(System.currentTimeMillis())
byte[] key = randomKey()
@@ -880,7 +880,7 @@ class JwtParserTest {
compact()
Jwt jwt = Jwts.parser().setSigningKey(key).
- expectIssuedAt(issuedAt).
+ requireIssuedAt(issuedAt).
parseClaimsJws(compact)
// system converts to seconds (lopping off millis precision), then returns millis
@@ -890,7 +890,7 @@ class JwtParserTest {
}
@Test
- void testParseExpectIssuedAt_Incorrect_Fail() {
+ void testParseRequireIssuedAt_Incorrect_Fail() {
def goodIssuedAt = new Date(System.currentTimeMillis())
def badIssuedAt = new Date(System.currentTimeMillis() - 10000)
@@ -902,7 +902,7 @@ class JwtParserTest {
try {
Jwts.parser().setSigningKey(key).
- expectIssuedAt(goodIssuedAt).
+ requireIssuedAt(goodIssuedAt).
parseClaimsJws(compact)
fail()
} catch(IncorrectClaimException e) {
@@ -914,7 +914,7 @@ class JwtParserTest {
}
@Test
- void testParseExpectIssuedAt_Missing_Fail() {
+ void testParseRequireIssuedAt_Missing_Fail() {
def issuedAt = new Date(System.currentTimeMillis() - 10000)
byte[] key = randomKey()
@@ -925,7 +925,7 @@ class JwtParserTest {
try {
Jwts.parser().setSigningKey(key).
- expectIssuedAt(issuedAt).
+ requireIssuedAt(issuedAt).
parseClaimsJws(compact)
fail()
} catch(MissingClaimException e) {
@@ -937,7 +937,7 @@ class JwtParserTest {
}
@Test
- void testParseExpectIssuer_Success() {
+ void testParseRequireIssuer_Success() {
def issuer = 'A Most Awesome Issuer'
byte[] key = randomKey()
@@ -947,14 +947,14 @@ class JwtParserTest {
compact()
Jwt jwt = Jwts.parser().setSigningKey(key).
- expectIssuer(issuer).
+ requireIssuer(issuer).
parseClaimsJws(compact)
assertEquals jwt.getBody().getIssuer(), issuer
}
@Test
- void testParseExpectIssuer_Incorrect_Fail() {
+ void testParseRequireIssuer_Incorrect_Fail() {
def goodIssuer = 'A Most Awesome Issuer'
def badIssuer = 'A Most Bogus Issuer'
@@ -966,7 +966,7 @@ class JwtParserTest {
try {
Jwts.parser().setSigningKey(key).
- expectIssuer(goodIssuer).
+ requireIssuer(goodIssuer).
parseClaimsJws(compact)
fail()
} catch(IncorrectClaimException e) {
@@ -978,7 +978,7 @@ class JwtParserTest {
}
@Test
- void testParseExpectIssuer_Missing_Fail() {
+ void testParseRequireIssuer_Missing_Fail() {
def issuer = 'A Most Awesome Issuer'
byte[] key = randomKey()
@@ -989,7 +989,7 @@ class JwtParserTest {
try {
Jwts.parser().setSigningKey(key).
- expectIssuer(issuer).
+ requireIssuer(issuer).
parseClaimsJws(compact)
fail()
} catch(MissingClaimException e) {
@@ -1001,7 +1001,7 @@ class JwtParserTest {
}
@Test
- void testParseExpectAudience_Success() {
+ void testParseRequireAudience_Success() {
def audience = 'A Most Awesome Audience'
byte[] key = randomKey()
@@ -1011,14 +1011,14 @@ class JwtParserTest {
compact()
Jwt jwt = Jwts.parser().setSigningKey(key).
- expectAudience(audience).
+ requireAudience(audience).
parseClaimsJws(compact)
assertEquals jwt.getBody().getAudience(), audience
}
@Test
- void testParseExpectAudience_Incorrect_Fail() {
+ void testParseRequireAudience_Incorrect_Fail() {
def goodAudience = 'A Most Awesome Audience'
def badAudience = 'A Most Bogus Audience'
@@ -1030,7 +1030,7 @@ class JwtParserTest {
try {
Jwts.parser().setSigningKey(key).
- expectAudience(goodAudience).
+ requireAudience(goodAudience).
parseClaimsJws(compact)
fail()
} catch(IncorrectClaimException e) {
@@ -1042,7 +1042,7 @@ class JwtParserTest {
}
@Test
- void testParseExpectAudience_Missing_Fail() {
+ void testParseRequireAudience_Missing_Fail() {
def audience = 'A Most Awesome audience'
byte[] key = randomKey()
@@ -1053,7 +1053,7 @@ class JwtParserTest {
try {
Jwts.parser().setSigningKey(key).
- expectAudience(audience).
+ requireAudience(audience).
parseClaimsJws(compact)
fail()
} catch(MissingClaimException e) {
@@ -1065,7 +1065,7 @@ class JwtParserTest {
}
@Test
- void testParseExpectSubject_Success() {
+ void testParseRequireSubject_Success() {
def subject = 'A Most Awesome Subject'
byte[] key = randomKey()
@@ -1075,14 +1075,14 @@ class JwtParserTest {
compact()
Jwt jwt = Jwts.parser().setSigningKey(key).
- expectSubject(subject).
+ requireSubject(subject).
parseClaimsJws(compact)
assertEquals jwt.getBody().getSubject(), subject
}
@Test
- void testParseExpectSubject_Incorrect_Fail() {
+ void testParseRequireSubject_Incorrect_Fail() {
def goodSubject = 'A Most Awesome Subject'
def badSubject = 'A Most Bogus Subject'
@@ -1094,7 +1094,7 @@ class JwtParserTest {
try {
Jwts.parser().setSigningKey(key).
- expectSubject(goodSubject).
+ requireSubject(goodSubject).
parseClaimsJws(compact)
fail()
} catch(IncorrectClaimException e) {
@@ -1106,7 +1106,7 @@ class JwtParserTest {
}
@Test
- void testParseExpectSubject_Missing_Fail() {
+ void testParseRequireSubject_Missing_Fail() {
def subject = 'A Most Awesome Subject'
byte[] key = randomKey()
@@ -1117,7 +1117,7 @@ class JwtParserTest {
try {
Jwts.parser().setSigningKey(key).
- expectSubject(subject).
+ requireSubject(subject).
parseClaimsJws(compact)
fail()
} catch(MissingClaimException e) {
@@ -1129,7 +1129,7 @@ class JwtParserTest {
}
@Test
- void testParseExpectId_Success() {
+ void testParseRequireId_Success() {
def id = 'A Most Awesome id'
byte[] key = randomKey()
@@ -1139,14 +1139,14 @@ class JwtParserTest {
compact()
Jwt jwt = Jwts.parser().setSigningKey(key).
- expectId(id).
+ requireId(id).
parseClaimsJws(compact)
assertEquals jwt.getBody().getId(), id
}
@Test
- void testParseExpectId_Incorrect_Fail() {
+ void testParseRequireId_Incorrect_Fail() {
def goodId = 'A Most Awesome Id'
def badId = 'A Most Bogus Id'
@@ -1158,7 +1158,7 @@ class JwtParserTest {
try {
Jwts.parser().setSigningKey(key).
- expectId(goodId).
+ requireId(goodId).
parseClaimsJws(compact)
fail()
} catch(IncorrectClaimException e) {
@@ -1170,7 +1170,7 @@ class JwtParserTest {
}
@Test
- void testParseExpectId_Missing_Fail() {
+ void testParseRequireId_Missing_Fail() {
def id = 'A Most Awesome Id'
byte[] key = randomKey()
@@ -1181,7 +1181,7 @@ class JwtParserTest {
try {
Jwts.parser().setSigningKey(key).
- expectId(id).
+ requireId(id).
parseClaimsJws(compact)
fail()
} catch(MissingClaimException e) {
From 2e452a42b10173ee0708eedacb845ba0b315c5d0 Mon Sep 17 00:00:00 2001
From: Micah Silverman
Date: Wed, 23 Sep 2015 04:33:31 -0400
Subject: [PATCH 18/50] Added requireExpiration and requireNotBefore
---
src/main/java/io/jsonwebtoken/JwtParser.java | 16 +++
.../jsonwebtoken/impl/DefaultJwtParser.java | 14 ++
.../io/jsonwebtoken/JwtParserTest.groovy | 136 ++++++++++++++++++
3 files changed, 166 insertions(+)
diff --git a/src/main/java/io/jsonwebtoken/JwtParser.java b/src/main/java/io/jsonwebtoken/JwtParser.java
index 1ed015ff..828563ea 100644
--- a/src/main/java/io/jsonwebtoken/JwtParser.java
+++ b/src/main/java/io/jsonwebtoken/JwtParser.java
@@ -67,6 +67,22 @@ public interface JwtParser {
*/
JwtParser requireIssuedAt(Date issuedAt);
+ /**
+ * Sets an expected value for the expiration claim.
+ *
+ * @param expiration
+ * @return the parser for method chaining.
+ */
+ JwtParser requireExpiration(Date expiration);
+
+ /**
+ * Sets an expected value for the notBefore claim.
+ *
+ * @param notBefore
+ * @return the parser for method chaining
+ */
+ JwtParser requireNotBefore(Date notBefore);
+
/**
* Sets an expected value for any given claim name.
*
diff --git a/src/main/java/io/jsonwebtoken/impl/DefaultJwtParser.java b/src/main/java/io/jsonwebtoken/impl/DefaultJwtParser.java
index a52e7b54..37c5f5a0 100644
--- a/src/main/java/io/jsonwebtoken/impl/DefaultJwtParser.java
+++ b/src/main/java/io/jsonwebtoken/impl/DefaultJwtParser.java
@@ -99,6 +99,20 @@ public class DefaultJwtParser implements JwtParser {
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.");
diff --git a/src/test/groovy/io/jsonwebtoken/JwtParserTest.groovy b/src/test/groovy/io/jsonwebtoken/JwtParserTest.groovy
index 4fbd36ca..a65aa4dc 100644
--- a/src/test/groovy/io/jsonwebtoken/JwtParserTest.groovy
+++ b/src/test/groovy/io/jsonwebtoken/JwtParserTest.groovy
@@ -1191,4 +1191,140 @@ class JwtParserTest {
)
}
}
+
+ @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()
+ )
+ }
+ }
}
From b4015be11e4337977f743fe0d78d949c14ec3ae8 Mon Sep 17 00:00:00 2001
From: Micah Silverman
Date: Wed, 23 Sep 2015 03:37:47 -0400
Subject: [PATCH 19/50] Added in test that does NOT work for custom claim with
Date type.
---
.../groovy/io/jsonwebtoken/JwtParserTest.groovy | 17 +++++++++++++++++
1 file changed, 17 insertions(+)
diff --git a/src/test/groovy/io/jsonwebtoken/JwtParserTest.groovy b/src/test/groovy/io/jsonwebtoken/JwtParserTest.groovy
index a65aa4dc..60299845 100644
--- a/src/test/groovy/io/jsonwebtoken/JwtParserTest.groovy
+++ b/src/test/groovy/io/jsonwebtoken/JwtParserTest.groovy
@@ -1327,4 +1327,21 @@ class JwtParserTest {
)
}
}
+
+// @Test
+// void testParseExpectedCustomDate_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).
+// expect("aDate", aDate).
+// parseClaimsJws(compact)
+//
+// assertEquals jwt.getBody().get("aDate"), aDate
+// }
}
From 5d320d22a55c4822195852d7fded90101a728eca Mon Sep 17 00:00:00 2001
From: Micah Silverman
Date: Wed, 23 Sep 2015 15:43:15 -0400
Subject: [PATCH 20/50] Handled generic require for Date. Added ability to
specify required type on get method of claim
---
src/main/java/io/jsonwebtoken/Claims.java | 1 +
.../jsonwebtoken/RequiredTypeException.java | 32 ++++++++
.../io/jsonwebtoken/impl/DefaultClaims.java | 17 ++++
.../jsonwebtoken/impl/DefaultJwtParser.java | 15 +---
.../io/jsonwebtoken/JwtParserTest.groovy | 80 +++++++++++++++----
.../RequiredTypeExceptionTest.groovy | 20 +++++
.../impl/DefaultClaimsTest.groovy | 78 ++++++++++++++++++
7 files changed, 214 insertions(+), 29 deletions(-)
create mode 100644 src/main/java/io/jsonwebtoken/RequiredTypeException.java
create mode 100644 src/test/groovy/io/jsonwebtoken/RequiredTypeExceptionTest.groovy
create mode 100644 src/test/groovy/io/jsonwebtoken/impl/DefaultClaimsTest.groovy
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/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..38df87ee 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;
@@ -106,4 +107,20 @@ 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 (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 37c5f5a0..9bbe3b2b 100644
--- a/src/main/java/io/jsonwebtoken/impl/DefaultJwtParser.java
+++ b/src/main/java/io/jsonwebtoken/impl/DefaultJwtParser.java
@@ -378,31 +378,20 @@ public class DefaultJwtParser implements JwtParser {
private void validateExpectedClaims(Header header, Claims claims) {
for (String expectedClaimName : expectedClaims.keySet()) {
- // this will be overridden if one of the default claims is used
Object expectedClaimValue = expectedClaims.get(expectedClaimName);
Object actualClaimValue = claims.get(expectedClaimName);
if (Claims.ISSUED_AT.equals(expectedClaimName)) {
expectedClaimValue = expectedClaims.getIssuedAt();
actualClaimValue = claims.getIssuedAt();
- } else if (Claims.AUDIENCE.equals(expectedClaimName)) {
- expectedClaimValue = expectedClaims.getAudience();
- actualClaimValue = claims.getAudience();
- } else if (Claims.ISSUER.equals(expectedClaimName)) {
- expectedClaimValue = expectedClaims.getIssuer();
- actualClaimValue = claims.getIssuer();
- } else if (Claims.SUBJECT.equals(expectedClaimName)) {
- expectedClaimValue = expectedClaims.getSubject();
- actualClaimValue = claims.getSubject();
} else if (Claims.EXPIRATION.equals(expectedClaimName)) {
expectedClaimValue = expectedClaims.getExpiration();
actualClaimValue = claims.getExpiration();
} else if (Claims.NOT_BEFORE.equals(expectedClaimName)) {
expectedClaimValue = expectedClaims.getNotBefore();
actualClaimValue = claims.getNotBefore();
- } else if (Claims.ID.equals(expectedClaimName)) {
- expectedClaimValue = expectedClaims.getId();
- actualClaimValue = claims.getId();
+ } else if (expectedClaimValue instanceof Date && actualClaimValue != null && actualClaimValue instanceof Long) {
+ actualClaimValue = new Date((Long)actualClaimValue);
}
InvalidClaimException invalidClaimException = null;
diff --git a/src/test/groovy/io/jsonwebtoken/JwtParserTest.groovy b/src/test/groovy/io/jsonwebtoken/JwtParserTest.groovy
index 60299845..3e37658f 100644
--- a/src/test/groovy/io/jsonwebtoken/JwtParserTest.groovy
+++ b/src/test/groovy/io/jsonwebtoken/JwtParserTest.groovy
@@ -1328,20 +1328,68 @@ class JwtParserTest {
}
}
-// @Test
-// void testParseExpectedCustomDate_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).
-// expect("aDate", aDate).
-// parseClaimsJws(compact)
-//
-// assertEquals jwt.getBody().get("aDate"), aDate
-// }
+ @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/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..cb584a4d
--- /dev/null
+++ b/src/test/groovy/io/jsonwebtoken/impl/DefaultClaimsTest.groovy
@@ -0,0 +1,78 @@
+/*
+ * 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);
+ }
+}
From 19f6fcaa51d9f64cfb20c136b002f7ef51c2b0a4 Mon Sep 17 00:00:00 2001
From: josebarrueta
Date: Wed, 23 Sep 2015 13:21:08 -0700
Subject: [PATCH 21/50] Issue-52 Adding ability to compress/decompress. Added
tests for happy path.
---
.gitignore | 1 +
.../io/jsonwebtoken/CompressionCodec.java | 30 +++++++
.../CompressionCodecResolver.java | 27 ++++++
.../io/jsonwebtoken/CompressionException.java | 33 +++++++
src/main/java/io/jsonwebtoken/Header.java | 7 ++
src/main/java/io/jsonwebtoken/JwtBuilder.java | 9 ++
src/main/java/io/jsonwebtoken/JwtParser.java | 8 ++
.../io/jsonwebtoken/impl/DefaultHeader.java | 12 +++
.../jsonwebtoken/impl/DefaultJwtBuilder.java | 46 ++++++++--
.../jsonwebtoken/impl/DefaultJwtParser.java | 26 +++++-
.../impl/compression/CompressionCodecs.java | 34 +++++++
.../DefaultCompressionCodecResolver.java | 52 +++++++++++
.../compression/DeflateCompressionCodec.java | 84 ++++++++++++++++++
.../compression/GzipCompressionCodec.java | 88 +++++++++++++++++++
.../java/io/jsonwebtoken/lang/Objects.java | 17 ++++
.../java/io/jsonwebtoken/lang/Strings.java | 3 +
.../CompressionExceptionTest.groovy | 41 +++++++++
.../groovy/io/jsonwebtoken/JwtsTest.groovy | 72 +++++++++++++++
.../impl/DefaultJwtBuilderTest.groovy | 2 +-
19 files changed, 581 insertions(+), 11 deletions(-)
create mode 100644 src/main/java/io/jsonwebtoken/CompressionCodec.java
create mode 100644 src/main/java/io/jsonwebtoken/CompressionCodecResolver.java
create mode 100644 src/main/java/io/jsonwebtoken/CompressionException.java
create mode 100644 src/main/java/io/jsonwebtoken/impl/compression/CompressionCodecs.java
create mode 100644 src/main/java/io/jsonwebtoken/impl/compression/DefaultCompressionCodecResolver.java
create mode 100644 src/main/java/io/jsonwebtoken/impl/compression/DeflateCompressionCodec.java
create mode 100644 src/main/java/io/jsonwebtoken/impl/compression/GzipCompressionCodec.java
create mode 100644 src/test/groovy/io/jsonwebtoken/CompressionExceptionTest.groovy
diff --git a/.gitignore b/.gitignore
index a14e67cf..611af4c7 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,4 +1,5 @@
*.class
+.DS_Store
# Mobile Tools for Java (J2ME)
.mtj.tmp/
diff --git a/src/main/java/io/jsonwebtoken/CompressionCodec.java b/src/main/java/io/jsonwebtoken/CompressionCodec.java
new file mode 100644
index 00000000..e6a24481
--- /dev/null
+++ b/src/main/java/io/jsonwebtoken/CompressionCodec.java
@@ -0,0 +1,30 @@
+/*
+ * 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;
+
+/**
+ * CompressionCodec
+ *
+ * @since 0.59
+ */
+public interface CompressionCodec {
+
+ String getAlgorithmName();
+
+ byte[] compress(byte[] payload);
+
+ byte[] decompress(byte[] compressed);
+}
\ No newline at end of file
diff --git a/src/main/java/io/jsonwebtoken/CompressionCodecResolver.java b/src/main/java/io/jsonwebtoken/CompressionCodecResolver.java
new file mode 100644
index 00000000..76805e06
--- /dev/null
+++ b/src/main/java/io/jsonwebtoken/CompressionCodecResolver.java
@@ -0,0 +1,27 @@
+/*
+ * 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;
+
+/**
+ * CompressionCodecResolver
+ *
+ * @since 0.5.2
+ */
+public interface CompressionCodecResolver {
+
+ CompressionCodec resolveCompressionCodec(Header header);
+
+}
diff --git a/src/main/java/io/jsonwebtoken/CompressionException.java b/src/main/java/io/jsonwebtoken/CompressionException.java
new file mode 100644
index 00000000..d4ac62d3
--- /dev/null
+++ b/src/main/java/io/jsonwebtoken/CompressionException.java
@@ -0,0 +1,33 @@
+/*
+ * 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 that either compressing or decompressing an JWT body failed.
+ *
+ * @since 0.5.2
+ */
+public class CompressionException extends JwtException {
+
+ public CompressionException(String message) {
+ super(message);
+ }
+
+ public CompressionException(String message, Throwable cause) {
+ super(message, cause);
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/io/jsonwebtoken/Header.java b/src/main/java/io/jsonwebtoken/Header.java
index 00893a58..f4b5cfbf 100644
--- a/src/main/java/io/jsonwebtoken/Header.java
+++ b/src/main/java/io/jsonwebtoken/Header.java
@@ -48,6 +48,9 @@ public interface Header> extends Map {
/** JWT {@code Content Type} header parameter name: "cty"
*/
public static final String CONTENT_TYPE = "cty";
+ /** JWT {@code Compression Algorithm} header parameter name: "calg"
*/
+ public static final String COMPRESSION_ALGORITHM = "calg";
+
/**
* Returns the
* typ
(type) header value or {@code null} if not present.
@@ -100,4 +103,8 @@ public interface Header> extends Map {
*/
T setContentType(String cty);
+ String getCompressionAlgorithm();
+
+ T setCompressionAlgorithm(String compressionAlgorithm);
+
}
diff --git a/src/main/java/io/jsonwebtoken/JwtBuilder.java b/src/main/java/io/jsonwebtoken/JwtBuilder.java
index cb5339b5..10ae9682 100644
--- a/src/main/java/io/jsonwebtoken/JwtBuilder.java
+++ b/src/main/java/io/jsonwebtoken/JwtBuilder.java
@@ -349,6 +349,15 @@ public interface JwtBuilder extends ClaimsMutator {
*/
JwtBuilder signWith(SignatureAlgorithm alg, Key key);
+ /**
+ * Compresses the JWT body before being signed.
+ *
+ * @param codec implementation of the {@link CompressionCodec} to be used.
+ * @return the builder for method chaining.
+ * @since 0.5.2
+ */
+ JwtBuilder compressWith(CompressionCodec codec);
+
/**
* Actually builds the JWT and serializes it to a compact, URL-safe string according to the
* JWT Compact Serialization
diff --git a/src/main/java/io/jsonwebtoken/JwtParser.java b/src/main/java/io/jsonwebtoken/JwtParser.java
index f4b190ba..909e73d0 100644
--- a/src/main/java/io/jsonwebtoken/JwtParser.java
+++ b/src/main/java/io/jsonwebtoken/JwtParser.java
@@ -107,6 +107,14 @@ public interface JwtParser {
*/
JwtParser setSigningKeyResolver(SigningKeyResolver signingKeyResolver);
+ /**
+ *
+ * @param compressionCodecResolver
+ * @return the parser for method chaining.
+ * @since 0.5.2
+ */
+ JwtParser setCompressionCodecResolver(CompressionCodecResolver compressionCodecResolver);
+
/**
* Returns {@code true} if the specified JWT compact string represents a signed JWT (aka a 'JWS'), {@code false}
* otherwise.
diff --git a/src/main/java/io/jsonwebtoken/impl/DefaultHeader.java b/src/main/java/io/jsonwebtoken/impl/DefaultHeader.java
index 52ffc8b9..74efdf9d 100644
--- a/src/main/java/io/jsonwebtoken/impl/DefaultHeader.java
+++ b/src/main/java/io/jsonwebtoken/impl/DefaultHeader.java
@@ -51,4 +51,16 @@ public class DefaultHeader> extends JwtMap implements Header
setValue(CONTENT_TYPE, cty);
return (T)this;
}
+
+ @Override
+ public String getCompressionAlgorithm() {
+ return getString(COMPRESSION_ALGORITHM);
+ }
+
+ @Override
+ public T setCompressionAlgorithm(String compressionAlgorithm) {
+ setValue(COMPRESSION_ALGORITHM, compressionAlgorithm);
+ return (T) this;
+ }
+
}
diff --git a/src/main/java/io/jsonwebtoken/impl/DefaultJwtBuilder.java b/src/main/java/io/jsonwebtoken/impl/DefaultJwtBuilder.java
index 6509ab00..76d3a2c3 100644
--- a/src/main/java/io/jsonwebtoken/impl/DefaultJwtBuilder.java
+++ b/src/main/java/io/jsonwebtoken/impl/DefaultJwtBuilder.java
@@ -18,6 +18,7 @@ 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;
import io.jsonwebtoken.JwsHeader;
import io.jsonwebtoken.JwtBuilder;
@@ -48,6 +49,8 @@ public class DefaultJwtBuilder implements JwtBuilder {
private Key key;
private byte[] keyBytes;
+ private CompressionCodec compressionCodec;
+
@Override
public JwtBuilder setHeader(Header header) {
this.header = header;
@@ -113,6 +116,13 @@ public class DefaultJwtBuilder implements JwtBuilder {
return this;
}
+ @Override
+ public JwtBuilder compressWith(CompressionCodec compressionCodec) {
+ Assert.notNull(compressionCodec, "compressionCodec cannot be null");
+ this.compressionCodec = compressionCodec;
+ return this;
+ }
+
@Override
public JwtBuilder setPayload(String payload) {
this.payload = payload;
@@ -279,11 +289,30 @@ public class DefaultJwtBuilder implements JwtBuilder {
jwsHeader.setAlgorithm(SignatureAlgorithm.NONE.getValue());
}
+ if (compressionCodec != null) {
+ jwsHeader.setCompressionAlgorithm(compressionCodec.getAlgorithmName());
+ }
+
String base64UrlEncodedHeader = base64UrlEncode(jwsHeader, "Unable to serialize header to json.");
- String base64UrlEncodedBody = this.payload != null ?
- TextCodec.BASE64URL.encode(this.payload) :
- base64UrlEncode(claims, "Unable to serialize claims object to json.");
+ String base64UrlEncodedBody;
+
+ if (compressionCodec != null) {
+
+ 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.");
+ }
+
+ base64UrlEncodedBody = TextCodec.BASE64URL.encode(compressionCodec.compress(bytes));
+
+ } else {
+ base64UrlEncodedBody = this.payload != null ?
+ TextCodec.BASE64URL.encode(this.payload) :
+ base64UrlEncode(claims, "Unable to serialize claims object to json.");
+ }
String jwt = base64UrlEncodedHeader + JwtParser.SEPARATOR_CHAR + base64UrlEncodedBody;
@@ -311,17 +340,18 @@ public class DefaultJwtBuilder implements JwtBuilder {
}
protected String base64UrlEncode(Object o, String errMsg) {
- String s;
+ byte[] bytes;
try {
- s = toJson(o);
+ bytes = toJson(o);
} catch (JsonProcessingException e) {
throw new IllegalStateException(errMsg, e);
}
- return TextCodec.BASE64URL.encode(s);
+ return TextCodec.BASE64URL.encode(bytes);
}
- protected String toJson(Object o) throws JsonProcessingException {
- return OBJECT_MAPPER.writeValueAsString(o);
+
+ protected byte[] toJson(Object object) throws JsonProcessingException {
+ return OBJECT_MAPPER.writeValueAsBytes(object);
}
}
diff --git a/src/main/java/io/jsonwebtoken/impl/DefaultJwtParser.java b/src/main/java/io/jsonwebtoken/impl/DefaultJwtParser.java
index ba888e04..a3b372b2 100644
--- a/src/main/java/io/jsonwebtoken/impl/DefaultJwtParser.java
+++ b/src/main/java/io/jsonwebtoken/impl/DefaultJwtParser.java
@@ -17,11 +17,12 @@ package io.jsonwebtoken.impl;
import com.fasterxml.jackson.databind.ObjectMapper;
import io.jsonwebtoken.Claims;
+import io.jsonwebtoken.CompressionCodec;
+import io.jsonwebtoken.CompressionCodecResolver;
import io.jsonwebtoken.ExpiredJwtException;
import io.jsonwebtoken.Header;
import io.jsonwebtoken.Jws;
import io.jsonwebtoken.JwsHeader;
-import io.jsonwebtoken.SigningKeyResolver;
import io.jsonwebtoken.Jwt;
import io.jsonwebtoken.JwtHandler;
import io.jsonwebtoken.JwtHandlerAdapter;
@@ -30,7 +31,9 @@ import io.jsonwebtoken.MalformedJwtException;
import io.jsonwebtoken.PrematureJwtException;
import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.SignatureException;
+import io.jsonwebtoken.SigningKeyResolver;
import io.jsonwebtoken.UnsupportedJwtException;
+import io.jsonwebtoken.impl.compression.DefaultCompressionCodecResolver;
import io.jsonwebtoken.impl.crypto.DefaultJwtSignatureValidator;
import io.jsonwebtoken.impl.crypto.JwtSignatureValidator;
import io.jsonwebtoken.lang.Assert;
@@ -58,6 +61,8 @@ public class DefaultJwtParser implements JwtParser {
private SigningKeyResolver signingKeyResolver;
+ private CompressionCodecResolver compressionCodecResolver = new DefaultCompressionCodecResolver();
+
@Override
public JwtParser setSigningKey(byte[] key) {
Assert.notEmpty(key, "signing key cannot be null or empty.");
@@ -86,6 +91,13 @@ public class DefaultJwtParser implements JwtParser {
return this;
}
+ @Override
+ public JwtParser setCompressionCodecResolver(CompressionCodecResolver compressionCodecResolver) {
+ Assert.notNull(compressionCodecResolver, "compressionCodecResolver cannot be null.");
+ this.compressionCodecResolver = compressionCodecResolver;
+ return this;
+ }
+
@Override
public boolean isSigned(String jwt) {
@@ -157,6 +169,8 @@ public class DefaultJwtParser implements JwtParser {
// =============== Header =================
Header header = null;
+ CompressionCodec compressionCodec = null;
+
if (base64UrlEncodedHeader != null) {
String origValue = TextCodec.BASE64URL.decodeToString(base64UrlEncodedHeader);
Map m = readValue(origValue);
@@ -166,10 +180,18 @@ public class DefaultJwtParser implements JwtParser {
} else {
header = new DefaultHeader(m);
}
+
+ compressionCodec = compressionCodecResolver.resolveCompressionCodec(header);
}
// =============== Body =================
- String payload = TextCodec.BASE64URL.decodeToString(base64UrlEncodedPayload);
+ String payload;
+ if (compressionCodec != null) {
+ byte[] decompressed = compressionCodec.decompress(TextCodec.BASE64URL.decode(base64UrlEncodedPayload));
+ payload = new String(decompressed, Strings.UTF_8);
+ } else {
+ payload = TextCodec.BASE64URL.decodeToString(base64UrlEncodedPayload);
+ }
Claims claims = null;
diff --git a/src/main/java/io/jsonwebtoken/impl/compression/CompressionCodecs.java b/src/main/java/io/jsonwebtoken/impl/compression/CompressionCodecs.java
new file mode 100644
index 00000000..d1d663d3
--- /dev/null
+++ b/src/main/java/io/jsonwebtoken/impl/compression/CompressionCodecs.java
@@ -0,0 +1,34 @@
+/*
+ * 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.compression;
+
+import io.jsonwebtoken.CompressionCodec;
+
+/**
+ * CompressionCodecs exposes default implementation of the {@link CompressionCodec} interface.
+ *
+ * @since 0.5.2
+ */
+public abstract class CompressionCodecs {
+
+ private CompressionCodecs(){}
+
+ public static final CompressionCodec DEFLATE = new DeflateCompressionCodec();
+
+ public static final CompressionCodec GZIP = new GzipCompressionCodec();
+
+
+}
diff --git a/src/main/java/io/jsonwebtoken/impl/compression/DefaultCompressionCodecResolver.java b/src/main/java/io/jsonwebtoken/impl/compression/DefaultCompressionCodecResolver.java
new file mode 100644
index 00000000..00b37a88
--- /dev/null
+++ b/src/main/java/io/jsonwebtoken/impl/compression/DefaultCompressionCodecResolver.java
@@ -0,0 +1,52 @@
+/*
+ * 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.compression;
+
+import io.jsonwebtoken.CompressionCodec;
+import io.jsonwebtoken.CompressionCodecResolver;
+import io.jsonwebtoken.CompressionException;
+import io.jsonwebtoken.Header;
+import io.jsonwebtoken.lang.Assert;
+import io.jsonwebtoken.lang.Strings;
+
+/**
+ * DefaultCompressionCodecResolver
+ *
+ * @since 0.5.2
+ */
+public class DefaultCompressionCodecResolver implements CompressionCodecResolver {
+
+ @Override
+ public CompressionCodec resolveCompressionCodec(Header header) {
+ Assert.notNull(header, "header cannot be null.");
+
+ String cmpAlg = header.getCompressionAlgorithm();
+
+ if (!Strings.hasText(cmpAlg)) {
+ return null;
+ }
+
+ if (CompressionCodecs.DEFLATE.getAlgorithmName().equalsIgnoreCase(cmpAlg)) {
+ return CompressionCodecs.DEFLATE;
+ }
+
+ if (CompressionCodecs.GZIP.getAlgorithmName().equalsIgnoreCase(cmpAlg)) {
+ return CompressionCodecs.GZIP;
+ }
+
+ throw new CompressionException("Unsupported compression algorithm '" + cmpAlg + "'");
+ }
+}
diff --git a/src/main/java/io/jsonwebtoken/impl/compression/DeflateCompressionCodec.java b/src/main/java/io/jsonwebtoken/impl/compression/DeflateCompressionCodec.java
new file mode 100644
index 00000000..83b5725d
--- /dev/null
+++ b/src/main/java/io/jsonwebtoken/impl/compression/DeflateCompressionCodec.java
@@ -0,0 +1,84 @@
+/*
+ * 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.compression;
+
+import io.jsonwebtoken.CompressionCodec;
+import io.jsonwebtoken.CompressionException;
+import io.jsonwebtoken.lang.Assert;
+import io.jsonwebtoken.lang.Objects;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.util.zip.Deflater;
+import java.util.zip.DeflaterOutputStream;
+import java.util.zip.InflaterOutputStream;
+
+/**
+ * DeflateCompressionCodec
+ *
+ * @since 0.5.2
+ */
+public class DeflateCompressionCodec implements CompressionCodec {
+
+ private static final String DEFLATE = "DEF";
+
+ @Override
+ public String getAlgorithmName() {
+ return DEFLATE;
+ }
+
+ @Override
+ public byte[] compress(byte[] payload) {
+ Assert.notNull(payload, "payload cannot be null.");
+
+ Deflater deflater = new Deflater(Deflater.BEST_COMPRESSION);
+
+ ByteArrayOutputStream outputStream = null;
+ DeflaterOutputStream deflaterOutputStream = null;
+ try {
+ outputStream = new ByteArrayOutputStream();
+ deflaterOutputStream = new DeflaterOutputStream(outputStream, deflater, true);
+
+ deflaterOutputStream.write(payload, 0, payload.length);
+ deflaterOutputStream.flush();
+ return outputStream.toByteArray();
+ } catch (IOException e) {
+ throw new CompressionException("Unable to compress payload.", e);
+ } finally {
+ Objects.nullSafeClose(outputStream, deflaterOutputStream);
+ }
+ }
+
+ @Override
+ public byte[] decompress(byte[] compressed) {
+ Assert.notNull(compressed, "compressed cannot be null.");
+
+ InflaterOutputStream inflaterOutputStream = null;
+ ByteArrayOutputStream decompressedOutputStream = null;
+
+ try {
+ decompressedOutputStream = new ByteArrayOutputStream();
+ inflaterOutputStream = new InflaterOutputStream(decompressedOutputStream);
+ inflaterOutputStream.write(compressed);
+ inflaterOutputStream.flush();
+ return decompressedOutputStream.toByteArray();
+ } catch (IOException e) {
+ throw new CompressionException("Unable to decompress compressed payload.", e);
+ } finally {
+ Objects.nullSafeClose(decompressedOutputStream, inflaterOutputStream);
+ }
+ }
+}
diff --git a/src/main/java/io/jsonwebtoken/impl/compression/GzipCompressionCodec.java b/src/main/java/io/jsonwebtoken/impl/compression/GzipCompressionCodec.java
new file mode 100644
index 00000000..1a8a7a53
--- /dev/null
+++ b/src/main/java/io/jsonwebtoken/impl/compression/GzipCompressionCodec.java
@@ -0,0 +1,88 @@
+/*
+ * 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.compression;
+
+import io.jsonwebtoken.CompressionCodec;
+import io.jsonwebtoken.CompressionException;
+import io.jsonwebtoken.lang.Assert;
+import io.jsonwebtoken.lang.Objects;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.util.zip.GZIPInputStream;
+import java.util.zip.GZIPOutputStream;
+
+/**
+ * GzipCompressionCodec
+ *
+ * @since 0.5.2
+ */
+public class GzipCompressionCodec implements CompressionCodec {
+
+ private static final String GZIP = "GZIP";
+
+ @Override
+ public String getAlgorithmName() {
+ return GZIP;
+ }
+
+ @Override
+ public byte[] compress(byte[] payload) {
+ Assert.notNull(payload, "payload cannot be null.");
+
+ ByteArrayOutputStream outputStream = null;
+ GZIPOutputStream gzipOutputStream = null;
+
+ try {
+ outputStream = new ByteArrayOutputStream();
+ gzipOutputStream = new GZIPOutputStream(outputStream, true);
+ gzipOutputStream.write(payload, 0, payload.length);
+ gzipOutputStream.finish();
+ return outputStream.toByteArray();
+ } catch (IOException e) {
+ throw new CompressionException("Unable to compress payload.", e);
+ } finally {
+ Objects.nullSafeClose(outputStream, gzipOutputStream);
+ }
+ }
+
+ @Override
+ public byte[] decompress(byte[] compressed) {
+ Assert.notNull(compressed, "compressed cannot be null.");
+
+ byte[] buffer = new byte[512];
+
+ ByteArrayOutputStream outputStream = null;
+ GZIPInputStream gzipInputStream = null;
+ ByteArrayInputStream inputStream = null;
+
+ try {
+ inputStream = new ByteArrayInputStream(compressed);
+ gzipInputStream = new GZIPInputStream(inputStream);
+ outputStream = new ByteArrayOutputStream();
+ int read;
+ while ((read = gzipInputStream.read(buffer)) != -1) {
+ outputStream.write(buffer, 0, read);
+ }
+ return outputStream.toByteArray();
+ } catch (IOException e) {
+ throw new CompressionException("Unable to decompress compressed payload.", e);
+ } finally {
+ Objects.nullSafeClose(inputStream, gzipInputStream, outputStream);
+ }
+ }
+}
diff --git a/src/main/java/io/jsonwebtoken/lang/Objects.java b/src/main/java/io/jsonwebtoken/lang/Objects.java
index dbdc84ce..acc4b288 100644
--- a/src/main/java/io/jsonwebtoken/lang/Objects.java
+++ b/src/main/java/io/jsonwebtoken/lang/Objects.java
@@ -15,6 +15,8 @@
*/
package io.jsonwebtoken.lang;
+import java.io.Closeable;
+import java.io.IOException;
import java.lang.reflect.Array;
import java.util.Arrays;
@@ -905,4 +907,19 @@ public abstract class Objects {
return sb.toString();
}
+ public static void nullSafeClose(Closeable... closeables) {
+ if (closeables == null) {
+ return;
+ }
+
+ for (Closeable closeable : closeables) {
+ if (closeable != null) {
+ try {
+ closeable.close();
+ } catch (IOException e) {
+ //Ignore the exception during close.
+ }
+ }
+ }
+ }
}
diff --git a/src/main/java/io/jsonwebtoken/lang/Strings.java b/src/main/java/io/jsonwebtoken/lang/Strings.java
index 3fa1b6ca..8e1157f4 100644
--- a/src/main/java/io/jsonwebtoken/lang/Strings.java
+++ b/src/main/java/io/jsonwebtoken/lang/Strings.java
@@ -15,6 +15,7 @@
*/
package io.jsonwebtoken.lang;
+import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
@@ -40,6 +41,8 @@ public abstract class Strings {
private static final char EXTENSION_SEPARATOR = '.';
+ public static final Charset UTF_8 = Charset.forName("UTF-8");
+
//---------------------------------------------------------------------
// General convenience methods for working with Strings
//---------------------------------------------------------------------
diff --git a/src/test/groovy/io/jsonwebtoken/CompressionExceptionTest.groovy b/src/test/groovy/io/jsonwebtoken/CompressionExceptionTest.groovy
new file mode 100644
index 00000000..f588ebbd
--- /dev/null
+++ b/src/test/groovy/io/jsonwebtoken/CompressionExceptionTest.groovy
@@ -0,0 +1,41 @@
+/*
+ * 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
+
+class CompressionExceptionTest {
+
+ @Test
+ void testDefaultConstructor() {
+ def exception = new CompressionException("my message")
+
+ assertEquals "my message", exception.getMessage()
+ }
+
+ @Test
+ void testConstructorWithCause() {
+
+ def ioException = new IOException("root error")
+
+ def exception = new CompressionException("wrapping", ioException)
+
+ assertEquals "wrapping", exception.getMessage()
+ assertEquals ioException, exception.getCause()
+ }
+}
\ No newline at end of file
diff --git a/src/test/groovy/io/jsonwebtoken/JwtsTest.groovy b/src/test/groovy/io/jsonwebtoken/JwtsTest.groovy
index b8e6306a..c20ceda4 100644
--- a/src/test/groovy/io/jsonwebtoken/JwtsTest.groovy
+++ b/src/test/groovy/io/jsonwebtoken/JwtsTest.groovy
@@ -19,6 +19,7 @@ import com.fasterxml.jackson.databind.ObjectMapper
import io.jsonwebtoken.impl.DefaultHeader
import io.jsonwebtoken.impl.DefaultJwsHeader
import io.jsonwebtoken.impl.TextCodec
+import io.jsonwebtoken.impl.compression.CompressionCodecs
import io.jsonwebtoken.impl.crypto.EllipticCurveProvider
import io.jsonwebtoken.impl.crypto.MacProvider
import io.jsonwebtoken.impl.crypto.RsaProvider
@@ -186,6 +187,16 @@ class JwtsTest {
}
}
+ @Test
+ void testWithInvalidCompressionAlgorithm() {
+ try {
+
+ Jwts.builder().setHeaderParam(Header.COMPRESSION_ALGORITHM, "CUSTOM").setId("andId").compact()
+ } catch (CompressionException e) {
+ assertEquals "Unsupported compression algorithm 'CUSTOM'", e.getMessage()
+ }
+ }
+
@Test
void testConvenienceIssuer() {
String compact = Jwts.builder().setIssuer("Me").compact();
@@ -320,6 +331,67 @@ class JwtsTest {
assertNull claims.getId()
}
+ @Test
+ void testCompressedJwtWithDeflate() {
+
+ byte[] key = MacProvider.generateKey().getEncoded()
+
+ String id = UUID.randomUUID().toString()
+
+ String compact = Jwts.builder().setId(id).setAudience("an audience").signWith(SignatureAlgorithm.HS256, key)
+ .claim("state", "hello this is an amazing jwt").compressWith(CompressionCodecs.DEFLATE).compact()
+
+ def jws = Jwts.parser().setSigningKey(key).parseClaimsJws(compact)
+
+ Claims claims = jws.body
+
+ assertEquals "DEF", jws.header.getCompressionAlgorithm()
+
+ assertEquals id, claims.getId()
+ assertEquals "an audience", claims.getAudience()
+ assertEquals "hello this is an amazing jwt", claims.state
+ }
+
+ @Test
+ void testCompressedJwtWithGZIP() {
+
+ byte[] key = MacProvider.generateKey().getEncoded()
+
+ String id = UUID.randomUUID().toString()
+
+ String compact = Jwts.builder().setId(id).setAudience("an audience").signWith(SignatureAlgorithm.HS256, key)
+ .claim("state", "hello this is an amazing jwt").compressWith(CompressionCodecs.GZIP).compact()
+
+ def jws = Jwts.parser().setSigningKey(key).parseClaimsJws(compact)
+
+ Claims claims = jws.body
+
+ assertEquals "GZIP", jws.header.getCompressionAlgorithm()
+
+ assertEquals id, claims.getId()
+ assertEquals "an audience", claims.getAudience()
+ assertEquals "hello this is an amazing jwt", claims.state
+ }
+
+ @Test
+ void testCompressStringPayloadWithDeflate() {
+
+ byte[] key = MacProvider.generateKey().getEncoded()
+
+ String payload = "this is my test for a payload"
+
+ String compact = Jwts.builder().setPayload(payload).signWith(SignatureAlgorithm.HS256, key)
+ .compressWith(CompressionCodecs.DEFLATE).compact()
+
+ def jws = Jwts.parser().setSigningKey(key).parsePlaintextJws(compact)
+
+ String parsed = jws.body
+
+ assertEquals "DEF", jws.header.getCompressionAlgorithm()
+
+ assertEquals "this is my test for a payload", parsed
+ }
+
@Test
void testHS256() {
testHmac(SignatureAlgorithm.HS256);
diff --git a/src/test/groovy/io/jsonwebtoken/impl/DefaultJwtBuilderTest.groovy b/src/test/groovy/io/jsonwebtoken/impl/DefaultJwtBuilderTest.groovy
index 7090d03c..4ee7d8e5 100644
--- a/src/test/groovy/io/jsonwebtoken/impl/DefaultJwtBuilderTest.groovy
+++ b/src/test/groovy/io/jsonwebtoken/impl/DefaultJwtBuilderTest.groovy
@@ -173,7 +173,7 @@ class DefaultJwtBuilderTest {
def b = new DefaultJwtBuilder() {
@Override
- protected String toJson(Object o) throws JsonProcessingException {
+ protected byte[] toJson(Object o) throws JsonProcessingException {
throw new JsonMappingException('foo')
}
}
From 6401727b2a0c263b81c4d465cdace778efc137b5 Mon Sep 17 00:00:00 2001
From: Micah Silverman
Date: Wed, 23 Sep 2015 17:03:07 -0400
Subject: [PATCH 22/50] Refactored generic get with required type to handle the
official claim types that are dates. Updated javadocs to reflect 'require'
language.
---
src/main/java/io/jsonwebtoken/JwtParser.java | 34 +++++++++++--------
.../io/jsonwebtoken/impl/DefaultClaims.java | 13 +++++--
.../jsonwebtoken/impl/DefaultJwtParser.java | 22 ++++++------
.../impl/DefaultClaimsTest.groovy | 29 ++++++++++++++--
4 files changed, 69 insertions(+), 29 deletions(-)
diff --git a/src/main/java/io/jsonwebtoken/JwtParser.java b/src/main/java/io/jsonwebtoken/JwtParser.java
index 828563ea..44019f77 100644
--- a/src/main/java/io/jsonwebtoken/JwtParser.java
+++ b/src/main/java/io/jsonwebtoken/JwtParser.java
@@ -28,15 +28,17 @@ public interface JwtParser {
public static final char SEPARATOR_CHAR = '.';
/**
- * Sets an expected value for the jti claim.
+ * Ensures that the specified {@code jti} value is present when parsing the JWT. If not present,
+ * an exception will be thrown indicating that the JWT is invalid and may not be used.
*
* @param id
- * @return the parser for method chaining.
+ * @return the parser method for chaining.
*/
JwtParser requireId(String id);
/**
- * Sets an expected value for the subject claim.
+ * Ensures that the specified {@code sub} value is present when parsing the JWT. If not present,
+ * an exception will be thrown indicating that the JWT is invalid and may not be used.
*
* @param subject
* @return the parser for method chaining.
@@ -44,7 +46,8 @@ public interface JwtParser {
JwtParser requireSubject(String subject);
/**
- * Sets an expected value for the audience claim.
+ * Ensures that the specified {@code aud} value is present when parsing the JWT. If not present,
+ * an exception will be thrown indicating that the JWT is invalid and may not be used.
*
* @param audience
* @return the parser for method chaining.
@@ -52,7 +55,8 @@ public interface JwtParser {
JwtParser requireAudience(String audience);
/**
- * Sets an expected value for the issuer claim.
+ * Ensures that the specified {@code iss} value is present when parsing the JWT. If not present,
+ * an exception will be thrown indicating that the JWT is invalid and may not be used.
*
* @param issuer
* @return the parser for method chaining.
@@ -60,7 +64,8 @@ public interface JwtParser {
JwtParser requireIssuer(String issuer);
/**
- * Sets an expected value for the issuedAt claim.
+ * Ensures that the specified {@code iat} value is present when parsing the JWT. If not present,
+ * an exception will be thrown indicating that the JWT is invalid and may not be used.
*
* @param issuedAt
* @return the parser for method chaining.
@@ -68,7 +73,8 @@ public interface JwtParser {
JwtParser requireIssuedAt(Date issuedAt);
/**
- * Sets an expected value for the expiration claim.
+ * Ensures that the specified {@code exp} value is present when parsing the JWT. If not present,
+ * an exception will be thrown indicating that the JWT is invalid and may not be used.
*
* @param expiration
* @return the parser for method chaining.
@@ -76,7 +82,8 @@ public interface JwtParser {
JwtParser requireExpiration(Date expiration);
/**
- * Sets an expected value for the notBefore claim.
+ * Ensures that the specified {@code nbf} value is present when parsing the JWT. If not present,
+ * an exception will be thrown indicating that the JWT is invalid and may not be used.
*
* @param notBefore
* @return the parser for method chaining
@@ -84,15 +91,14 @@ public interface JwtParser {
JwtParser requireNotBefore(Date notBefore);
/**
- * Sets an expected value for any given claim name.
+ * Ensures that the specified {@code claimName} value is present when parsing the JWT. If not present,
+ * an exception will be thrown indicating that the JWT is invalid and may not be used.
*
- * If an expectation is set for a particular claim name and the JWT being parsed does not have that claim set,
+ * If a particular claim is required and the JWT being parsed does not have that claim set,
* a {@Link MissingClaimException} will be thrown.
*
- * If an expectation is set for a particular claim name and the JWT being parsed has a value that is different than
- * the expected value, a {@link IncorrectClaimException} will be thrown.
- *
- * If either {@code claimName} is null or empty or {@code value} is null, the expectation is simply ignored.
+ * If a particular claim is required and the JWT being parsed has a value that is different than
+ * the required value, a {@link IncorrectClaimException} will be thrown.
*
* @param claimName
* @param value
diff --git a/src/main/java/io/jsonwebtoken/impl/DefaultClaims.java b/src/main/java/io/jsonwebtoken/impl/DefaultClaims.java
index 38df87ee..196c82ff 100644
--- a/src/main/java/io/jsonwebtoken/impl/DefaultClaims.java
+++ b/src/main/java/io/jsonwebtoken/impl/DefaultClaims.java
@@ -66,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
@@ -77,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
@@ -88,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
@@ -113,6 +113,13 @@ public class DefaultClaims extends JwtMap implements Claims {
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);
}
diff --git a/src/main/java/io/jsonwebtoken/impl/DefaultJwtParser.java b/src/main/java/io/jsonwebtoken/impl/DefaultJwtParser.java
index 9bbe3b2b..a759549d 100644
--- a/src/main/java/io/jsonwebtoken/impl/DefaultJwtParser.java
+++ b/src/main/java/io/jsonwebtoken/impl/DefaultJwtParser.java
@@ -381,16 +381,18 @@ public class DefaultJwtParser implements JwtParser {
Object expectedClaimValue = expectedClaims.get(expectedClaimName);
Object actualClaimValue = claims.get(expectedClaimName);
- if (Claims.ISSUED_AT.equals(expectedClaimName)) {
- expectedClaimValue = expectedClaims.getIssuedAt();
- actualClaimValue = claims.getIssuedAt();
- } else if (Claims.EXPIRATION.equals(expectedClaimName)) {
- expectedClaimValue = expectedClaims.getExpiration();
- actualClaimValue = claims.getExpiration();
- } else if (Claims.NOT_BEFORE.equals(expectedClaimName)) {
- expectedClaimValue = expectedClaims.getNotBefore();
- actualClaimValue = claims.getNotBefore();
- } else if (expectedClaimValue instanceof Date && actualClaimValue != null && actualClaimValue instanceof Long) {
+ 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);
}
diff --git a/src/test/groovy/io/jsonwebtoken/impl/DefaultClaimsTest.groovy b/src/test/groovy/io/jsonwebtoken/impl/DefaultClaimsTest.groovy
index cb584a4d..161957f0 100644
--- a/src/test/groovy/io/jsonwebtoken/impl/DefaultClaimsTest.groovy
+++ b/src/test/groovy/io/jsonwebtoken/impl/DefaultClaimsTest.groovy
@@ -64,7 +64,7 @@ class DefaultClaimsTest {
def actual = new Date();
claims.put("aDate", actual)
Date expected = claims.get("aDate", Date.class);
- assertEquals(expected, actual);
+ assertEquals(expected, actual)
}
@Test
@@ -73,6 +73,31 @@ class DefaultClaimsTest {
// note that Long is stored in claim
claims.put("aDate", actual.getTime())
Date expected = claims.get("aDate", Date.class);
- assertEquals(expected, actual);
+ 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())
+ }
+
}
From 681a3fc0bad92e61a15f4df36c7d10b7ebcc4dd7 Mon Sep 17 00:00:00 2001
From: Micah Silverman
Date: Wed, 23 Sep 2015 17:20:02 -0400
Subject: [PATCH 23/50] Added coverage test for JwtMap.
---
src/test/groovy/io/jsonwebtoken/impl/JwtMapTest.groovy | 6 ++++++
1 file changed, 6 insertions(+)
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() {
From a22a76ad79d202798a2f36518cb7a7288df56ff2 Mon Sep 17 00:00:00 2001
From: Micah Silverman
Date: Wed, 23 Sep 2015 17:40:50 -0400
Subject: [PATCH 24/50] Update to javadocs to make more clear.
---
src/main/java/io/jsonwebtoken/JwtParser.java | 62 +++++++++++++-------
1 file changed, 40 insertions(+), 22 deletions(-)
diff --git a/src/main/java/io/jsonwebtoken/JwtParser.java b/src/main/java/io/jsonwebtoken/JwtParser.java
index 44019f77..dd9b1271 100644
--- a/src/main/java/io/jsonwebtoken/JwtParser.java
+++ b/src/main/java/io/jsonwebtoken/JwtParser.java
@@ -28,81 +28,99 @@ public interface JwtParser {
public static final char SEPARATOR_CHAR = '.';
/**
- * Ensures that the specified {@code jti} value is present when parsing the JWT. If not present,
- * an exception will be thrown indicating that the JWT is invalid and may not be used.
+ * 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} value is present when parsing the JWT. If not present,
- * an exception will be thrown indicating that the JWT is invalid and may not be used.
+ * 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} value is present when parsing the JWT. If not present,
- * an exception will be thrown indicating that the JWT is invalid and may not be used.
+ * 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} value is present when parsing the JWT. If not present,
- * an exception will be thrown indicating that the JWT is invalid and may not be used.
+ * 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} value is present when parsing the JWT. If not present,
- * an exception will be thrown indicating that the JWT is invalid and may not be used.
+ * 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} value is present when parsing the JWT. If not present,
- * an exception will be thrown indicating that the JWT is invalid and may not be used.
+ * 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} value is present when parsing the JWT. If not present,
- * an exception will be thrown indicating that the JWT is invalid and may not be used.
+ * 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} value is present when parsing the JWT. If not present,
- * an exception will be thrown indicating that the JWT is invalid and may not be used.
- *
- * If a particular claim is required and the JWT being parsed does not have that claim set,
- * a {@Link MissingClaimException} will be thrown.
- *
- * If a particular claim is required and the JWT being parsed has a value that is different than
- * the required value, a {@link IncorrectClaimException} will be thrown.
+ * 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);
From 137b6db31d1876b967238491ff04d744ea96b2ac Mon Sep 17 00:00:00 2001
From: Les Hazlewood
Date: Wed, 23 Sep 2015 15:05:30 -0700
Subject: [PATCH 25/50] #42: Added readme/release documentation for required
JWT field value functionality.
---
README.md | 38 +++++++++++++++++++++++++++++++++++++-
1 file changed, 37 insertions(+), 1 deletion(-)
diff --git a/README.md b/README.md
index 7ccdb2b3..ccac0a77 100644
--- a/README.md
+++ b/README.md
@@ -101,7 +101,43 @@ These feature sets will be implemented in a future release when possible. Commu
### 0.6
-- Added the ability to set expectations when parsing a JWT which enforces a particular claim having a particular value
+#### 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(name, requiredValue)` 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
From 3e80cd647b59effb91aaf1b14e082345466b05a0 Mon Sep 17 00:00:00 2001
From: Les Hazlewood
Date: Wed, 23 Sep 2015 15:07:46 -0700
Subject: [PATCH 26/50] Minor readability update
---
README.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/README.md b/README.md
index ccac0a77..655a333f 100644
--- a/README.md
+++ b/README.md
@@ -128,7 +128,7 @@ try {
}
```
-You can also require custom fields by using the `require(name, requiredValue)` method - for example:
+You can also require custom fields by using the `require(fieldName, requiredFieldValue)` method - for example:
```java
try {
From 806844a89a279f8e57fbee9121a03695cf68215b Mon Sep 17 00:00:00 2001
From: Jason Erickson
Date: Wed, 23 Sep 2015 15:44:07 -0700
Subject: [PATCH 27/50] Issue-52: Refactoring and adding unit tests to cover
the compression functionality
---
.../io/jsonwebtoken/CompressionCodec.java | 18 ++++-
.../CompressionCodecResolver.java | 8 +-
.../compression/BaseCompressionCodec.java | 79 ++++++++++++++++++
.../impl/compression/CompressionCodecs.java | 15 ++--
.../DefaultCompressionCodecResolver.java | 18 +++--
.../compression/DeflateCompressionCodec.java | 19 +----
.../compression/GzipCompressionCodec.java | 44 ++++------
.../groovy/io/jsonwebtoken/JwtsTest.groovy | 80 +++++++++++++++++++
.../BaseCompressionCodecTest.groovy | 55 +++++++++++++
9 files changed, 275 insertions(+), 61 deletions(-)
create mode 100644 src/main/java/io/jsonwebtoken/impl/compression/BaseCompressionCodec.java
create mode 100644 src/test/groovy/io/jsonwebtoken/impl/compression/BaseCompressionCodecTest.groovy
diff --git a/src/main/java/io/jsonwebtoken/CompressionCodec.java b/src/main/java/io/jsonwebtoken/CompressionCodec.java
index e6a24481..84c56a29 100644
--- a/src/main/java/io/jsonwebtoken/CompressionCodec.java
+++ b/src/main/java/io/jsonwebtoken/CompressionCodec.java
@@ -16,15 +16,29 @@
package io.jsonwebtoken;
/**
- * CompressionCodec
+ * Defines how to compress and decompress byte arrays.
*
- * @since 0.59
+ * @since 0.5.2
*/
public interface CompressionCodec {
+ /**
+ * The algorithm name that would appear in the JWT header.
+ * @return the algorithm name that would appear in the JWT header
+ */
String getAlgorithmName();
+ /**
+ * Takes a byte array and returns a compressed version.
+ * @param payload bytes to compress
+ * @return compressed bytes
+ */
byte[] compress(byte[] payload);
+ /**
+ * Takes a compressed byte array and returns a decompressed version.
+ * @param compressed compressed bytes
+ * @return decompressed bytes
+ */
byte[] decompress(byte[] compressed);
}
\ No newline at end of file
diff --git a/src/main/java/io/jsonwebtoken/CompressionCodecResolver.java b/src/main/java/io/jsonwebtoken/CompressionCodecResolver.java
index 76805e06..877ab890 100644
--- a/src/main/java/io/jsonwebtoken/CompressionCodecResolver.java
+++ b/src/main/java/io/jsonwebtoken/CompressionCodecResolver.java
@@ -16,12 +16,16 @@
package io.jsonwebtoken;
/**
- * CompressionCodecResolver
+ * Resolves "calg" header to an implementation of CompressionCodec.
*
* @since 0.5.2
*/
public interface CompressionCodecResolver {
-
+ /**
+ * Examines the header and returns a CompressionCodec if it finds one that it recognizes.
+ * @param header of the JWT
+ * @return CompressionCodec matching the "calg" header, or null if there is no "calg" header.
+ */
CompressionCodec resolveCompressionCodec(Header header);
}
diff --git a/src/main/java/io/jsonwebtoken/impl/compression/BaseCompressionCodec.java b/src/main/java/io/jsonwebtoken/impl/compression/BaseCompressionCodec.java
new file mode 100644
index 00000000..1c86613a
--- /dev/null
+++ b/src/main/java/io/jsonwebtoken/impl/compression/BaseCompressionCodec.java
@@ -0,0 +1,79 @@
+/*
+ * 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.compression;
+
+import io.jsonwebtoken.CompressionCodec;
+import io.jsonwebtoken.CompressionException;
+import io.jsonwebtoken.lang.Assert;
+
+import java.io.IOException;
+
+/**
+ * Base class that asserts arguments and wraps IOException with CompressionException.
+ *
+ * @since 0.5.2
+ */
+public abstract class BaseCompressionCodec implements CompressionCodec {
+ /**
+ * Implement this method to do the actual work of compressing the payload
+ * @param payload the bytes to compress
+ * @return the compressed bytes
+ * @throws IOException if the compression causes an IOException
+ */
+ protected abstract byte[] doCompress(byte[] payload) throws IOException;
+
+ /**
+ * Asserts that payload is not null and calls doCompress
+ * @param payload bytes to compress
+ * @return compressed bytes
+ * @throws CompressionException if doCompress throws an IOException
+ */
+ @Override
+ public final byte[] compress(byte[] payload) {
+ Assert.notNull(payload, "payload cannot be null.");
+
+ try {
+ return doCompress(payload);
+ } catch (IOException e) {
+ throw new CompressionException("Unable to compress payload.", e);
+ }
+ }
+
+ /**
+ * Asserts the compressed bytes is not null and calls doDecompress
+ * @param compressed compressed bytes
+ * @return decompressed bytes
+ * @throws CompressionException if doCompress throws an IOException
+ */
+ @Override
+ public final byte[] decompress(byte[] compressed) {
+ Assert.notNull(compressed, "compressed bytes cannot be null.");
+
+ try {
+ return doDecompress(compressed);
+ } catch (IOException e) {
+ throw new CompressionException("Unable to decompress bytes.", e);
+ }
+ }
+
+ /**
+ * Implement this method to do the actual work of decompressing the compressed bytes.
+ * @param compressed compressed bytes
+ * @return decompressed bytes
+ * @throws IOException if the decompression runs into an IO problem
+ */
+ protected abstract byte[] doDecompress(byte[] compressed) throws IOException;
+}
diff --git a/src/main/java/io/jsonwebtoken/impl/compression/CompressionCodecs.java b/src/main/java/io/jsonwebtoken/impl/compression/CompressionCodecs.java
index d1d663d3..8a1fd7eb 100644
--- a/src/main/java/io/jsonwebtoken/impl/compression/CompressionCodecs.java
+++ b/src/main/java/io/jsonwebtoken/impl/compression/CompressionCodecs.java
@@ -22,13 +22,16 @@ import io.jsonwebtoken.CompressionCodec;
*
* @since 0.5.2
*/
-public abstract class CompressionCodecs {
+public interface CompressionCodecs {
- private CompressionCodecs(){}
-
- public static final CompressionCodec DEFLATE = new DeflateCompressionCodec();
-
- public static final CompressionCodec GZIP = new GzipCompressionCodec();
+ /**
+ * Codec implementing the deflate compression algorithm
+ */
+ CompressionCodec DEFLATE = new DeflateCompressionCodec();
+ /**
+ * Codec implementing the gzip compression algorithm
+ */
+ CompressionCodec GZIP = new GzipCompressionCodec();
}
diff --git a/src/main/java/io/jsonwebtoken/impl/compression/DefaultCompressionCodecResolver.java b/src/main/java/io/jsonwebtoken/impl/compression/DefaultCompressionCodecResolver.java
index 00b37a88..298c3b57 100644
--- a/src/main/java/io/jsonwebtoken/impl/compression/DefaultCompressionCodecResolver.java
+++ b/src/main/java/io/jsonwebtoken/impl/compression/DefaultCompressionCodecResolver.java
@@ -23,7 +23,8 @@ import io.jsonwebtoken.lang.Assert;
import io.jsonwebtoken.lang.Strings;
/**
- * DefaultCompressionCodecResolver
+ * Default implementation of {@link CompressionCodecResolver}. This implementation will resolve DEF to
+ * {@link DeflateCompressionCodec} and GZIP to {@link GzipCompressionCodec}.
*
* @since 0.5.2
*/
@@ -31,22 +32,25 @@ public class DefaultCompressionCodecResolver implements CompressionCodecResolver
@Override
public CompressionCodec resolveCompressionCodec(Header header) {
- Assert.notNull(header, "header cannot be null.");
+ String cmpAlg = getAlgorithmFromHeader(header);
- String cmpAlg = header.getCompressionAlgorithm();
-
- if (!Strings.hasText(cmpAlg)) {
+ final boolean hasCompressionAlgorithm = Strings.hasText(cmpAlg);
+ if (!hasCompressionAlgorithm) {
return null;
}
-
if (CompressionCodecs.DEFLATE.getAlgorithmName().equalsIgnoreCase(cmpAlg)) {
return CompressionCodecs.DEFLATE;
}
-
if (CompressionCodecs.GZIP.getAlgorithmName().equalsIgnoreCase(cmpAlg)) {
return CompressionCodecs.GZIP;
}
throw new CompressionException("Unsupported compression algorithm '" + cmpAlg + "'");
}
+
+ protected final String getAlgorithmFromHeader(Header header) {
+ Assert.notNull(header, "header cannot be null.");
+
+ return header.getCompressionAlgorithm();
+ }
}
diff --git a/src/main/java/io/jsonwebtoken/impl/compression/DeflateCompressionCodec.java b/src/main/java/io/jsonwebtoken/impl/compression/DeflateCompressionCodec.java
index 83b5725d..e7390fbd 100644
--- a/src/main/java/io/jsonwebtoken/impl/compression/DeflateCompressionCodec.java
+++ b/src/main/java/io/jsonwebtoken/impl/compression/DeflateCompressionCodec.java
@@ -15,9 +15,6 @@
*/
package io.jsonwebtoken.impl.compression;
-import io.jsonwebtoken.CompressionCodec;
-import io.jsonwebtoken.CompressionException;
-import io.jsonwebtoken.lang.Assert;
import io.jsonwebtoken.lang.Objects;
import java.io.ByteArrayOutputStream;
@@ -27,11 +24,10 @@ import java.util.zip.DeflaterOutputStream;
import java.util.zip.InflaterOutputStream;
/**
- * DeflateCompressionCodec
- *
+ * Codec implementing the deflate compression algorithm
* @since 0.5.2
*/
-public class DeflateCompressionCodec implements CompressionCodec {
+public class DeflateCompressionCodec extends BaseCompressionCodec {
private static final String DEFLATE = "DEF";
@@ -41,8 +37,7 @@ public class DeflateCompressionCodec implements CompressionCodec {
}
@Override
- public byte[] compress(byte[] payload) {
- Assert.notNull(payload, "payload cannot be null.");
+ public byte[] doCompress(byte[] payload) throws IOException {
Deflater deflater = new Deflater(Deflater.BEST_COMPRESSION);
@@ -55,17 +50,13 @@ public class DeflateCompressionCodec implements CompressionCodec {
deflaterOutputStream.write(payload, 0, payload.length);
deflaterOutputStream.flush();
return outputStream.toByteArray();
- } catch (IOException e) {
- throw new CompressionException("Unable to compress payload.", e);
} finally {
Objects.nullSafeClose(outputStream, deflaterOutputStream);
}
}
@Override
- public byte[] decompress(byte[] compressed) {
- Assert.notNull(compressed, "compressed cannot be null.");
-
+ public byte[] doDecompress(byte[] compressed) throws IOException {
InflaterOutputStream inflaterOutputStream = null;
ByteArrayOutputStream decompressedOutputStream = null;
@@ -75,8 +66,6 @@ public class DeflateCompressionCodec implements CompressionCodec {
inflaterOutputStream.write(compressed);
inflaterOutputStream.flush();
return decompressedOutputStream.toByteArray();
- } catch (IOException e) {
- throw new CompressionException("Unable to decompress compressed payload.", e);
} finally {
Objects.nullSafeClose(decompressedOutputStream, inflaterOutputStream);
}
diff --git a/src/main/java/io/jsonwebtoken/impl/compression/GzipCompressionCodec.java b/src/main/java/io/jsonwebtoken/impl/compression/GzipCompressionCodec.java
index 1a8a7a53..61fd98de 100644
--- a/src/main/java/io/jsonwebtoken/impl/compression/GzipCompressionCodec.java
+++ b/src/main/java/io/jsonwebtoken/impl/compression/GzipCompressionCodec.java
@@ -16,8 +16,6 @@
package io.jsonwebtoken.impl.compression;
import io.jsonwebtoken.CompressionCodec;
-import io.jsonwebtoken.CompressionException;
-import io.jsonwebtoken.lang.Assert;
import io.jsonwebtoken.lang.Objects;
import java.io.ByteArrayInputStream;
@@ -27,11 +25,11 @@ import java.util.zip.GZIPInputStream;
import java.util.zip.GZIPOutputStream;
/**
- * GzipCompressionCodec
+ * Codec implementing the gzip compression algorithm
*
* @since 0.5.2
*/
-public class GzipCompressionCodec implements CompressionCodec {
+public class GzipCompressionCodec extends BaseCompressionCodec implements CompressionCodec {
private static final String GZIP = "GZIP";
@@ -41,29 +39,7 @@ public class GzipCompressionCodec implements CompressionCodec {
}
@Override
- public byte[] compress(byte[] payload) {
- Assert.notNull(payload, "payload cannot be null.");
-
- ByteArrayOutputStream outputStream = null;
- GZIPOutputStream gzipOutputStream = null;
-
- try {
- outputStream = new ByteArrayOutputStream();
- gzipOutputStream = new GZIPOutputStream(outputStream, true);
- gzipOutputStream.write(payload, 0, payload.length);
- gzipOutputStream.finish();
- return outputStream.toByteArray();
- } catch (IOException e) {
- throw new CompressionException("Unable to compress payload.", e);
- } finally {
- Objects.nullSafeClose(outputStream, gzipOutputStream);
- }
- }
-
- @Override
- public byte[] decompress(byte[] compressed) {
- Assert.notNull(compressed, "compressed cannot be null.");
-
+ protected byte[] doDecompress(byte[] compressed) throws IOException {
byte[] buffer = new byte[512];
ByteArrayOutputStream outputStream = null;
@@ -79,10 +55,20 @@ public class GzipCompressionCodec implements CompressionCodec {
outputStream.write(buffer, 0, read);
}
return outputStream.toByteArray();
- } catch (IOException e) {
- throw new CompressionException("Unable to decompress compressed payload.", e);
} finally {
Objects.nullSafeClose(inputStream, gzipInputStream, outputStream);
}
}
+
+ protected byte[] doCompress(byte[] payload) throws IOException {
+ ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+ GZIPOutputStream compressorOutputStream = new GZIPOutputStream(outputStream, true);
+ try {
+ compressorOutputStream.write(payload, 0, payload.length);
+ compressorOutputStream.finish();
+ return outputStream.toByteArray();
+ } finally {
+ Objects.nullSafeClose(compressorOutputStream, outputStream);
+ }
+ }
}
diff --git a/src/test/groovy/io/jsonwebtoken/JwtsTest.groovy b/src/test/groovy/io/jsonwebtoken/JwtsTest.groovy
index c20ceda4..c188ca56 100644
--- a/src/test/groovy/io/jsonwebtoken/JwtsTest.groovy
+++ b/src/test/groovy/io/jsonwebtoken/JwtsTest.groovy
@@ -20,9 +20,12 @@ import io.jsonwebtoken.impl.DefaultHeader
import io.jsonwebtoken.impl.DefaultJwsHeader
import io.jsonwebtoken.impl.TextCodec
import io.jsonwebtoken.impl.compression.CompressionCodecs
+import io.jsonwebtoken.impl.compression.DefaultCompressionCodecResolver
+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.lang.Strings
import org.junit.Test
import javax.crypto.Mac
@@ -109,6 +112,7 @@ class JwtsTest {
def token = Jwts.parser().parse(jwt);
+ //noinspection GrEqualsBetweenInconvertibleTypes
assert token.body == claims
}
@@ -331,6 +335,27 @@ class JwtsTest {
assertNull claims.getId()
}
+ @Test
+ void testUncompressedJwt() {
+
+ byte[] key = MacProvider.generateKey().getEncoded()
+
+ String id = UUID.randomUUID().toString()
+
+ String compact = Jwts.builder().setId(id).setAudience("an audience").signWith(SignatureAlgorithm.HS256, key)
+ .claim("state", "hello this is an amazing jwt").compact()
+
+ def jws = Jwts.parser().setSigningKey(key).parseClaimsJws(compact)
+
+ Claims claims = jws.body
+
+ assertNull jws.header.getCompressionAlgorithm()
+
+ assertEquals id, claims.getId()
+ assertEquals "an audience", claims.getAudience()
+ assertEquals "hello this is an amazing jwt", claims.state
+ }
+
@Test
void testCompressedJwtWithDeflate() {
@@ -373,6 +398,58 @@ class JwtsTest {
assertEquals "hello this is an amazing jwt", claims.state
}
+ @Test
+ void testCompressedWithCustomResolver() {
+ byte[] key = MacProvider.generateKey().getEncoded()
+
+ String id = UUID.randomUUID().toString()
+
+ String compact = Jwts.builder().setId(id).setAudience("an audience").signWith(SignatureAlgorithm.HS256, key)
+ .claim("state", "hello this is an amazing jwt").compressWith(new GzipCompressionCodec() {
+ @Override
+ String getAlgorithmName() {
+ return "CUSTOM"
+ }
+ }).compact()
+
+ def jws = Jwts.parser().setSigningKey(key).setCompressionCodecResolver(new DefaultCompressionCodecResolver() {
+ @Override
+ CompressionCodec resolveCompressionCodec(Header header) {
+ String algorithm = getAlgorithmFromHeader(header);
+ if ("CUSTOM".equals(algorithm)) {
+ return CompressionCodecs.GZIP
+ } else {
+ return null
+ }
+ }
+ }).parseClaimsJws(compact)
+
+ Claims claims = jws.body
+
+ assertEquals "CUSTOM", jws.header.getCompressionAlgorithm()
+
+ assertEquals id, claims.getId()
+ assertEquals "an audience", claims.getAudience()
+ assertEquals "hello this is an amazing jwt", claims.state
+
+ }
+ @Test(expected = CompressionException.class)
+ void testCompressedJwtWithUnrecognizedHeader() {
+ byte[] key = MacProvider.generateKey().getEncoded()
+
+ String id = UUID.randomUUID().toString()
+
+ String compact = Jwts.builder().setId(id).setAudience("an audience").signWith(SignatureAlgorithm.HS256, key)
+ .claim("state", "hello this is an amazing jwt").compressWith(new GzipCompressionCodec() {
+ @Override
+ String getAlgorithmName() {
+ return "CUSTOM"
+ }
+ }).compact()
+
+ Jwts.parser().setSigningKey(key).parseClaimsJws(compact)
+ }
+
@Test
void testCompressStringPayloadWithDeflate() {
@@ -643,6 +720,7 @@ class JwtsTest {
def token = Jwts.parser().setSigningKey(key).parse(jwt);
assert [alg: alg.name()] == token.header
+ //noinspection GrEqualsBetweenInconvertibleTypes
assert token.body == claims
}
@@ -657,6 +735,7 @@ class JwtsTest {
def token = Jwts.parser().setSigningKey(key).parse(jwt)
assert token.header == [alg: alg.name()]
+ //noinspection GrEqualsBetweenInconvertibleTypes
assert token.body == claims
}
@@ -678,6 +757,7 @@ class JwtsTest {
def token = Jwts.parser().setSigningKey(key).parse(jwt);
assert token.header == [alg: alg.name()]
+ //noinspection GrEqualsBetweenInconvertibleTypes
assert token.body == claims
}
}
diff --git a/src/test/groovy/io/jsonwebtoken/impl/compression/BaseCompressionCodecTest.groovy b/src/test/groovy/io/jsonwebtoken/impl/compression/BaseCompressionCodecTest.groovy
new file mode 100644
index 00000000..7ffc8eca
--- /dev/null
+++ b/src/test/groovy/io/jsonwebtoken/impl/compression/BaseCompressionCodecTest.groovy
@@ -0,0 +1,55 @@
+/*
+ * 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.compression
+
+import io.jsonwebtoken.CompressionCodec
+import io.jsonwebtoken.CompressionException
+import org.junit.Test
+
+/**
+ * @since 0.5.2
+ */
+class BaseCompressionCodecTest {
+ static class ExceptionThrowingCodec extends BaseCompressionCodec {
+
+ @Override
+ protected byte[] doCompress(byte[] payload) throws IOException {
+ throw new IOException("Test Exception")
+ }
+
+ @Override
+ String getAlgorithmName() {
+ return "Test"
+ }
+
+ @Override
+ protected byte[] doDecompress(byte[] payload) throws IOException {
+ throw new IOException("Test Decompress Exception");
+ }
+ }
+
+ @Test(expected = CompressionException.class)
+ void testCompressWithException() {
+ CompressionCodec codecUT = new ExceptionThrowingCodec();
+ codecUT.compress(new byte[0]);
+ }
+
+ @Test(expected = CompressionException.class)
+ void testDecompressWithException() {
+ CompressionCodec codecUT = new ExceptionThrowingCodec();
+ codecUT.decompress(new byte[0]);
+ }
+}
From 7e15e2de022d7c110c7524cbd7ab9ef5b2c12bab Mon Sep 17 00:00:00 2001
From: Jason Erickson
Date: Wed, 23 Sep 2015 17:24:47 -0700
Subject: [PATCH 28/50] Issue-52: Refactoring and adding unit tests to cover
the compression functionality
---
.../impl/compression/DefaultCompressionCodecResolver.java | 2 +-
src/test/groovy/io/jsonwebtoken/JwtsTest.groovy | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/src/main/java/io/jsonwebtoken/impl/compression/DefaultCompressionCodecResolver.java b/src/main/java/io/jsonwebtoken/impl/compression/DefaultCompressionCodecResolver.java
index 298c3b57..4dde29d5 100644
--- a/src/main/java/io/jsonwebtoken/impl/compression/DefaultCompressionCodecResolver.java
+++ b/src/main/java/io/jsonwebtoken/impl/compression/DefaultCompressionCodecResolver.java
@@ -48,7 +48,7 @@ public class DefaultCompressionCodecResolver implements CompressionCodecResolver
throw new CompressionException("Unsupported compression algorithm '" + cmpAlg + "'");
}
- protected final String getAlgorithmFromHeader(Header header) {
+ private String getAlgorithmFromHeader(Header header) {
Assert.notNull(header, "header cannot be null.");
return header.getCompressionAlgorithm();
diff --git a/src/test/groovy/io/jsonwebtoken/JwtsTest.groovy b/src/test/groovy/io/jsonwebtoken/JwtsTest.groovy
index c188ca56..9a344f0b 100644
--- a/src/test/groovy/io/jsonwebtoken/JwtsTest.groovy
+++ b/src/test/groovy/io/jsonwebtoken/JwtsTest.groovy
@@ -415,7 +415,7 @@ class JwtsTest {
def jws = Jwts.parser().setSigningKey(key).setCompressionCodecResolver(new DefaultCompressionCodecResolver() {
@Override
CompressionCodec resolveCompressionCodec(Header header) {
- String algorithm = getAlgorithmFromHeader(header);
+ String algorithm = header.getCompressionAlgorithm()
if ("CUSTOM".equals(algorithm)) {
return CompressionCodecs.GZIP
} else {
From fef553ad72ef0c2aa49ea3f5abddd23f08668657 Mon Sep 17 00:00:00 2001
From: josebarrueta
Date: Fri, 9 Oct 2015 18:07:06 -0700
Subject: [PATCH 29/50] Issue-52 Improving Javadoc for compression
---
.../io/jsonwebtoken/CompressionCodec.java | 2 ++
src/main/java/io/jsonwebtoken/Header.java | 19 ++++++++++++++++++-
src/main/java/io/jsonwebtoken/JwtBuilder.java | 5 ++++-
src/main/java/io/jsonwebtoken/JwtParser.java | 4 +++-
4 files changed, 27 insertions(+), 3 deletions(-)
diff --git a/src/main/java/io/jsonwebtoken/CompressionCodec.java b/src/main/java/io/jsonwebtoken/CompressionCodec.java
index 84c56a29..35f03886 100644
--- a/src/main/java/io/jsonwebtoken/CompressionCodec.java
+++ b/src/main/java/io/jsonwebtoken/CompressionCodec.java
@@ -19,6 +19,8 @@ package io.jsonwebtoken;
* Defines how to compress and decompress byte arrays.
*
* @since 0.5.2
+ * @see io.jsonwebtoken.impl.compression.DeflateCompressionCodec
+ * @see io.jsonwebtoken.impl.compression.GzipCompressionCodec
*/
public interface CompressionCodec {
diff --git a/src/main/java/io/jsonwebtoken/Header.java b/src/main/java/io/jsonwebtoken/Header.java
index f4b5cfbf..81068c73 100644
--- a/src/main/java/io/jsonwebtoken/Header.java
+++ b/src/main/java/io/jsonwebtoken/Header.java
@@ -103,8 +103,25 @@ public interface Header> extends Map {
*/
T setContentType(String cty);
+ /**
+ * Returns the JWT calg
(Compression Algorithm) header value or {@code null} if not present.
+ *
+ * @return the {@code calg} header parameter value or {@code null} if not present.
+ * @since 0.5.2
+ */
String getCompressionAlgorithm();
- T setCompressionAlgorithm(String compressionAlgorithm);
+ /**
+ * Sets the JWT calg
(Compression Algorithm) header parameter value. A {@code null} value will remove
+ * the property from the JSON map.
+ *
+ *
The compression algorithm is NOT part of the JJWT's default {@link JwtParser} implementation supports both the
+ * {@link io.jsonwebtoken.impl.compression.DeflateCompressionCodec DEFLATE}
+ * and {@link io.jsonwebtoken.impl.compression.GzipCompressionCodec GZIP} algorithms by default - you do not need to
+ * specify a {@code CompressionCodecResolver} in these cases.
+ *
+ * However, if you want to use a compression algorithm other than {@code DEF} or {@code GZIP}, you must implement
+ * your own {@link CompressionCodecResolver} and specify that when
+ * {@link io.jsonwebtoken.JwtBuilder#compressWith(CompressionCodec) building} and
+ * {@link io.jsonwebtoken.JwtParser#setCompressionCodecResolver(CompressionCodecResolver) parsing} JWTs.
*
* @since 0.5.2
*/
public interface CompressionCodecResolver {
+
/**
- * Examines the header and returns a CompressionCodec if it finds one that it recognizes.
+ * Looks for a JWT {@code calg} header, and if found, returns the corresponding {@link CompressionCodec} the parser
+ * can use to decompress the JWT body.
+ *
* @param header of the JWT
- * @return CompressionCodec matching the "calg" header, or null if there is no "calg" header.
+ * @return CompressionCodec matching the {@code calg} header, or null if there is no {@code calg} header.
+ * @throws CompressionException if a {@code calg} header value is found and not supported.
*/
- CompressionCodec resolveCompressionCodec(Header header);
+ CompressionCodec resolveCompressionCodec(Header header) throws CompressionException;
}
diff --git a/src/main/java/io/jsonwebtoken/JwtBuilder.java b/src/main/java/io/jsonwebtoken/JwtBuilder.java
index b7985ef3..f88d2b0f 100644
--- a/src/main/java/io/jsonwebtoken/JwtBuilder.java
+++ b/src/main/java/io/jsonwebtoken/JwtBuilder.java
@@ -350,10 +350,18 @@ public interface JwtBuilder extends ClaimsMutator {
JwtBuilder signWith(SignatureAlgorithm alg, Key key);
/**
- * Compresses the JWT body using the {@link CompressionCodec} passed as argument.
+ * Compresses the JWT body using the specified {@link CompressionCodec}.
*
- * Note: Compression is not part of the Json Web Token specification and is not expected that other libraries (including
- * older versions of this one) are able to consume a compressed JWT body correctly.
+ * If your compact JWTs are large, and you want to reduce their total size during network transmission, this
+ * can be useful. For example, when embedding JWTs in URLs, some browsers may not support URLs longer than a
+ * certain length. Using compression can help ensure the compact JWT fits within that length. However, NOTE:
+ *
+ * WARNING: Compression is not defined by the JWT Specification, and it is not expected that other libraries
+ * (including JJWT versions < 0.5.2) are able to consume a compressed JWT body correctly. Only use this method
+ * if you are sure that you will consume the JWT with JJWT >= 0.5.2 or another library that you know implements
+ * the same behavior.
+ *
+ * @see io.jsonwebtoken.impl.compression.CompressionCodecs
*
* @param codec implementation of the {@link CompressionCodec} to be used.
* @return the builder for method chaining.
diff --git a/src/main/java/io/jsonwebtoken/JwtParser.java b/src/main/java/io/jsonwebtoken/JwtParser.java
index 5e5dcabd..88daf131 100644
--- a/src/main/java/io/jsonwebtoken/JwtParser.java
+++ b/src/main/java/io/jsonwebtoken/JwtParser.java
@@ -127,10 +127,10 @@ public interface JwtParser {
/**
* 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.
- *
+ *
*
Note that this key MUST be a valid key for the signature algorithm found in the JWT header
* (as the {@code alg} header parameter).
- *
+ *
*
This method overwrites any previously set key.
*
* @param key the algorithm-specific signature verification key used to validate any discovered JWS digital
@@ -142,12 +142,12 @@ public interface JwtParser {
/**
* 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.
- *
+ *
*
Note that this key MUST be a valid key for the signature algorithm found in the JWT header
* (as the {@code alg} header parameter).
- *
+ *
*
This method overwrites any previously set key.
- *
+ *
*
This is a convenience method: the string argument is first BASE64-decoded to a byte array and this resulting
* byte array is used to invoke {@link #setSigningKey(byte[])}.
*
@@ -160,12 +160,12 @@ public interface JwtParser {
/**
* 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.
- *
+ *
*
Note that this key MUST be a valid key for the signature algorithm found in the JWT header
* (as the {@code alg} header parameter).
- *
+ *
*
This method overwrites any previously set key.
- *
+ *
*
This is a convenience method: the string argument is first BASE64-decoded to a byte array and this resulting
* byte array is used to invoke {@link #setSigningKey(byte[])}.
*
@@ -178,12 +178,12 @@ public interface JwtParser {
/**
* Sets the {@link SigningKeyResolver} used to acquire the signing key
that should be used to verify
* a JWS's signature. If the parsed String is not a JWS (no signature), this resolver is not used.
- *
+ *
*
Specifying a {@code SigningKeyResolver} is necessary when the signing key is not already known before parsing
* the JWT and the JWT header or payload (plaintext body or Claims) must be inspected first to determine how to
* look up the signing key. Once returned by the resolver, the JwtParser will then verify the JWS signature with the
* returned key. For example:
- *
+ *
*
* Jws<Claims> jws = Jwts.parser().setSigningKeyResolver(new SigningKeyResolverAdapter() {
* @Override
@@ -193,9 +193,9 @@ public interface JwtParser {
* }})
* .parseClaimsJws(compact);
*
- *
+ *
*
A {@code SigningKeyResolver} is invoked once during parsing before the signature is verified.
- *
+ *
*
This method should only be used if a signing key is not provided by the other {@code setSigningKey*} builder
* methods.
*
@@ -206,10 +206,22 @@ public interface JwtParser {
JwtParser setSigningKeyResolver(SigningKeyResolver signingKeyResolver);
/**
- * Sets the {@link CompressionCodecResolver} used to acquire the {@link CompressionCodec} that should be used to verify
- * a decompress the JWT body. If the parsed JWT is not compressed, this resolver si not used.
+ * Sets the {@link CompressionCodecResolver} used to acquire the {@link CompressionCodec} that should be used to
+ * decompress the JWT body. If the parsed JWT is not compressed, this resolver is not used.
+ * NOTE: Compression is not defined by the JWT Specification, and it is not expected that other libraries
+ * (including JJWT versions < 0.5.2) are able to consume a compressed JWT body correctly. This method is only
+ * useful if the compact JWT was compressed with JJWT >= 0.5.2 or another library that you know implements
+ * the same behavior.
+ * Default Support
+ * JJWT's default {@link JwtParser} implementation supports both the
+ * {@link io.jsonwebtoken.impl.compression.DeflateCompressionCodec DEFLATE}
+ * and {@link io.jsonwebtoken.impl.compression.GzipCompressionCodec GZIP} algorithms by default - you do not need to
+ * specify a {@code CompressionCodecResolver} in these cases.
+ * However, if you want to use a compression algorithm other than {@code DEF} or {@code GZIP}, you must implement
+ * your own {@link CompressionCodecResolver} and specify that via this method and also when
+ * {@link io.jsonwebtoken.JwtBuilder#compressWith(CompressionCodec) building} JWTs.
*
- * @param compressionCodecResolver the compression codec resolver used to decompress the JWT body.
+ * @param compressionCodecResolver the compression codec resolver used to decompress the JWT body.
* @return the parser for method chaining.
* @since 0.5.2
*/
@@ -218,7 +230,7 @@ public interface JwtParser {
/**
* Returns {@code true} if the specified JWT compact string represents a signed JWT (aka a 'JWS'), {@code false}
* otherwise.
- *
+ *
*
Note that if you are reasonably sure that the token is signed, it is more efficient to attempt to
* parse the token (and catching exceptions if necessary) instead of calling this method first before parsing.
*
@@ -231,7 +243,7 @@ public interface JwtParser {
/**
* Parses the specified compact serialized JWT string based on the builder's current configuration state and
* returns the resulting JWT or JWS instance.
- *
+ *
*
This method returns a JWT or JWS based on the parsed string. Because it may be cumbersome to determine if it
* is a JWT or JWS, or if the body/payload is a Claims or String with {@code instanceof} checks, the
* {@link #parse(String, JwtHandler) parse(String,JwtHandler)} method allows for a type-safe callback approach that
@@ -258,11 +270,11 @@ public interface JwtParser {
/**
* Parses the specified compact serialized JWT string based on the builder's current configuration state and
* invokes the specified {@code handler} with the resulting JWT or JWS instance.
- *
+ *
*
If you are confident of the format of the JWT before parsing, you can create an anonymous subclass using the
* {@link io.jsonwebtoken.JwtHandlerAdapter JwtHandlerAdapter} and override only the methods you know are relevant
* for your use case(s), for example:
- *
+ *
*
* String compactJwt = request.getParameter("jwt"); //we are confident this is a signed JWS
*
@@ -273,10 +285,10 @@ public interface JwtParser {
* }
* });
*
- *
+ *
*
If you know the JWT string can be only one type of JWT, then it is even easier to invoke one of the
* following convenience methods instead of this one:
- *
+ *
*
* - {@link #parsePlaintextJwt(String)}
* - {@link #parseClaimsJwt(String)}
@@ -302,17 +314,17 @@ public interface JwtParser {
* @since 0.2
*/
T parse(String jwt, JwtHandler 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
* returns
* the resulting unsigned plaintext JWT instance.
- *
+ *
*
This is a convenience method that is usable if you are confident that the compact string argument reflects an
* unsigned plaintext JWT. An unsigned plaintext JWT has a String (non-JSON) body payload and it is not
* cryptographically signed.
- *
+ *
*
If the compact string presented does not reflect an unsigned plaintext JWT with non-JSON string body,
* an {@link UnsupportedJwtException} will be thrown.
*
@@ -332,17 +344,17 @@ public interface JwtParser {
* @since 0.2
*/
Jwt 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
* returns
* the resulting unsigned plaintext JWT instance.
- *
+ *
*
This is a convenience method that is usable if you are confident that the compact string argument reflects an
* unsigned Claims JWT. An unsigned Claims JWT has a {@link Claims} body and it is not cryptographically
* signed.
- *
+ *
*
If the compact string presented does not reflect an unsigned Claims JWT, an
* {@link UnsupportedJwtException} will be thrown.
*
@@ -363,17 +375,17 @@ public interface JwtParser {
* @since 0.2
*/
Jwt 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
* returns
* the resulting plaintext JWS instance.
- *
+ *
*
This is a convenience method that is usable if you are confident that the compact string argument reflects a
* plaintext JWS. A plaintext JWS is a JWT with a String (non-JSON) body (payload) that has been
* cryptographically signed.
- *
+ *
*
If the compact string presented does not reflect a plaintext JWS, an {@link UnsupportedJwtException}
* will be thrown.
*
@@ -391,16 +403,16 @@ public interface JwtParser {
* @since 0.2
*/
Jws 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
* returns
* the resulting Claims JWS instance.
- *
+ *
*
This is a convenience method that is usable if you are confident that the compact string argument reflects a
* Claims JWS. A Claims JWS is a JWT with a {@link Claims} body that has been cryptographically signed.
- *
+ *
*
If the compact string presented does not reflect a Claims JWS, an {@link UnsupportedJwtException} will be
* thrown.
*
@@ -420,5 +432,5 @@ public interface JwtParser {
* @since 0.2
*/
Jws parseClaimsJws(String claimsJws)
- throws ExpiredJwtException, UnsupportedJwtException, MalformedJwtException, SignatureException, IllegalArgumentException;
+ throws ExpiredJwtException, UnsupportedJwtException, MalformedJwtException, SignatureException, IllegalArgumentException;
}
diff --git a/src/main/java/io/jsonwebtoken/impl/DefaultJwtParser.java b/src/main/java/io/jsonwebtoken/impl/DefaultJwtParser.java
index df5d654f..a5d68aec 100644
--- a/src/main/java/io/jsonwebtoken/impl/DefaultJwtParser.java
+++ b/src/main/java/io/jsonwebtoken/impl/DefaultJwtParser.java
@@ -26,13 +26,12 @@ 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;
import io.jsonwebtoken.JwtParser;
import io.jsonwebtoken.MalformedJwtException;
+import io.jsonwebtoken.MissingClaimException;
import io.jsonwebtoken.PrematureJwtException;
import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.SignatureException;
diff --git a/src/main/java/io/jsonwebtoken/impl/compression/BaseCompressionCodec.java b/src/main/java/io/jsonwebtoken/impl/compression/AbstractCompressionCodec.java
similarity index 79%
rename from src/main/java/io/jsonwebtoken/impl/compression/BaseCompressionCodec.java
rename to src/main/java/io/jsonwebtoken/impl/compression/AbstractCompressionCodec.java
index 1c86613a..9457b68b 100644
--- a/src/main/java/io/jsonwebtoken/impl/compression/BaseCompressionCodec.java
+++ b/src/main/java/io/jsonwebtoken/impl/compression/AbstractCompressionCodec.java
@@ -22,13 +22,15 @@ import io.jsonwebtoken.lang.Assert;
import java.io.IOException;
/**
- * Base class that asserts arguments and wraps IOException with CompressionException.
+ * Abstract class that asserts arguments and wraps IOException with CompressionException.
*
* @since 0.5.2
*/
-public abstract class BaseCompressionCodec implements CompressionCodec {
+public abstract class AbstractCompressionCodec implements CompressionCodec {
+
/**
* Implement this method to do the actual work of compressing the payload
+ *
* @param payload the bytes to compress
* @return the compressed bytes
* @throws IOException if the compression causes an IOException
@@ -36,10 +38,11 @@ public abstract class BaseCompressionCodec implements CompressionCodec {
protected abstract byte[] doCompress(byte[] payload) throws IOException;
/**
- * Asserts that payload is not null and calls doCompress
+ * Asserts that payload is not null and calls {@link #doCompress(byte[]) doCompress}
+ *
* @param payload bytes to compress
* @return compressed bytes
- * @throws CompressionException if doCompress throws an IOException
+ * @throws CompressionException if {@link #doCompress(byte[]) doCompress} throws an IOException
*/
@Override
public final byte[] compress(byte[] payload) {
@@ -53,10 +56,11 @@ public abstract class BaseCompressionCodec implements CompressionCodec {
}
/**
- * Asserts the compressed bytes is not null and calls doDecompress
+ * Asserts the compressed bytes is not null and calls {@link #doDecompress(byte[]) doDecompress}
+ *
* @param compressed compressed bytes
* @return decompressed bytes
- * @throws CompressionException if doCompress throws an IOException
+ * @throws CompressionException if {@link #doDecompress(byte[]) doDecompress} throws an IOException
*/
@Override
public final byte[] decompress(byte[] compressed) {
@@ -71,6 +75,7 @@ public abstract class BaseCompressionCodec implements CompressionCodec {
/**
* Implement this method to do the actual work of decompressing the compressed bytes.
+ *
* @param compressed compressed bytes
* @return decompressed bytes
* @throws IOException if the decompression runs into an IO problem
diff --git a/src/main/java/io/jsonwebtoken/impl/compression/CompressionCodecs.java b/src/main/java/io/jsonwebtoken/impl/compression/CompressionCodecs.java
index 8a1fd7eb..f81721cd 100644
--- a/src/main/java/io/jsonwebtoken/impl/compression/CompressionCodecs.java
+++ b/src/main/java/io/jsonwebtoken/impl/compression/CompressionCodecs.java
@@ -18,20 +18,27 @@ package io.jsonwebtoken.impl.compression;
import io.jsonwebtoken.CompressionCodec;
/**
- * CompressionCodecs exposes default implementation of the {@link CompressionCodec} interface.
+ * Provides default implementations of the {@link CompressionCodec} interface.
+ *
+ * @see #DEFLATE
+ * @see #GZIP
*
* @since 0.5.2
*/
-public interface CompressionCodecs {
+public final class CompressionCodecs {
+
+ private static final CompressionCodecs I = new CompressionCodecs();
+
+ private CompressionCodecs(){} //prevent external instantiation
/**
* Codec implementing the deflate compression algorithm
*/
- CompressionCodec DEFLATE = new DeflateCompressionCodec();
+ public static final CompressionCodec DEFLATE = new DeflateCompressionCodec();
/**
* Codec implementing the gzip compression algorithm
*/
- CompressionCodec GZIP = new GzipCompressionCodec();
+ public static final CompressionCodec GZIP = new GzipCompressionCodec();
}
diff --git a/src/main/java/io/jsonwebtoken/impl/compression/DefaultCompressionCodecResolver.java b/src/main/java/io/jsonwebtoken/impl/compression/DefaultCompressionCodecResolver.java
index 4dde29d5..159a6f44 100644
--- a/src/main/java/io/jsonwebtoken/impl/compression/DefaultCompressionCodecResolver.java
+++ b/src/main/java/io/jsonwebtoken/impl/compression/DefaultCompressionCodecResolver.java
@@ -23,9 +23,24 @@ import io.jsonwebtoken.lang.Assert;
import io.jsonwebtoken.lang.Strings;
/**
- * Default implementation of {@link CompressionCodecResolver}. This implementation will resolve DEF to
- * {@link DeflateCompressionCodec} and GZIP to {@link GzipCompressionCodec}.
+ * Default implementation of {@link CompressionCodecResolver} that supports the following:
+ *
+ *
+ * - If the specified JWT {@link Header} does not have a {@code calg} header, this implementation does
+ * nothing and returns {@code null} to the caller, indicating no compression was used.
+ * - If the header has a {@code calg} value of {@code DEF}, a {@link DeflateCompressionCodec} will be returned.
+ * - If the header has a {@code calg} value of {@code GZIP}, a {@link GzipCompressionCodec} will be returned.
+ * - If the header has any other {@code calg} value, a {@link CompressionException} is thrown to reflect an
+ * unrecognized algorithm.
+ *
*
+ * If you want to use a compression algorithm other than {@code DEF} or {@code GZIP}, you must implement your own
+ * {@link CompressionCodecResolver} and specify that when
+ * {@link io.jsonwebtoken.JwtBuilder#compressWith(CompressionCodec) building} and
+ * {@link io.jsonwebtoken.JwtParser#setCompressionCodecResolver(CompressionCodecResolver) parsing} JWTs.
+ *
+ * @see DeflateCompressionCodec
+ * @see GzipCompressionCodec
* @since 0.5.2
*/
public class DefaultCompressionCodecResolver implements CompressionCodecResolver {
@@ -35,6 +50,7 @@ public class DefaultCompressionCodecResolver implements CompressionCodecResolver
String cmpAlg = getAlgorithmFromHeader(header);
final boolean hasCompressionAlgorithm = Strings.hasText(cmpAlg);
+
if (!hasCompressionAlgorithm) {
return null;
}
diff --git a/src/main/java/io/jsonwebtoken/impl/compression/DeflateCompressionCodec.java b/src/main/java/io/jsonwebtoken/impl/compression/DeflateCompressionCodec.java
index e7390fbd..90ee9dbc 100644
--- a/src/main/java/io/jsonwebtoken/impl/compression/DeflateCompressionCodec.java
+++ b/src/main/java/io/jsonwebtoken/impl/compression/DeflateCompressionCodec.java
@@ -24,10 +24,11 @@ import java.util.zip.DeflaterOutputStream;
import java.util.zip.InflaterOutputStream;
/**
- * Codec implementing the deflate compression algorithm
+ * Codec implementing the deflate compression algorithm.
+ *
* @since 0.5.2
*/
-public class DeflateCompressionCodec extends BaseCompressionCodec {
+public class DeflateCompressionCodec extends AbstractCompressionCodec {
private static final String DEFLATE = "DEF";
diff --git a/src/main/java/io/jsonwebtoken/impl/compression/GzipCompressionCodec.java b/src/main/java/io/jsonwebtoken/impl/compression/GzipCompressionCodec.java
index 61fd98de..f15531e1 100644
--- a/src/main/java/io/jsonwebtoken/impl/compression/GzipCompressionCodec.java
+++ b/src/main/java/io/jsonwebtoken/impl/compression/GzipCompressionCodec.java
@@ -25,11 +25,11 @@ import java.util.zip.GZIPInputStream;
import java.util.zip.GZIPOutputStream;
/**
- * Codec implementing the gzip compression algorithm
+ * Codec implementing the gzip compression algorithm.
*
* @since 0.5.2
*/
-public class GzipCompressionCodec extends BaseCompressionCodec implements CompressionCodec {
+public class GzipCompressionCodec extends AbstractCompressionCodec implements CompressionCodec {
private static final String GZIP = "GZIP";
diff --git a/src/test/groovy/io/jsonwebtoken/impl/compression/BaseCompressionCodecTest.groovy b/src/test/groovy/io/jsonwebtoken/impl/compression/AbstractCompressionCodecTest.groovy
similarity index 93%
rename from src/test/groovy/io/jsonwebtoken/impl/compression/BaseCompressionCodecTest.groovy
rename to src/test/groovy/io/jsonwebtoken/impl/compression/AbstractCompressionCodecTest.groovy
index 7ffc8eca..dc497c37 100644
--- a/src/test/groovy/io/jsonwebtoken/impl/compression/BaseCompressionCodecTest.groovy
+++ b/src/test/groovy/io/jsonwebtoken/impl/compression/AbstractCompressionCodecTest.groovy
@@ -22,8 +22,8 @@ import org.junit.Test
/**
* @since 0.5.2
*/
-class BaseCompressionCodecTest {
- static class ExceptionThrowingCodec extends BaseCompressionCodec {
+class AbstractCompressionCodecTest {
+ static class ExceptionThrowingCodec extends AbstractCompressionCodec {
@Override
protected byte[] doCompress(byte[] payload) throws IOException {
From 4d230a07256401a170911786de180886db28a003 Mon Sep 17 00:00:00 2001
From: Les Hazlewood
Date: Mon, 12 Oct 2015 14:17:13 -0700
Subject: [PATCH 31/50] #58: added toString implementations for JwtMap,
DefaultJwt and DefaultJws with tests
---
.../java/io/jsonwebtoken/impl/DefaultJws.java | 5 +++++
.../java/io/jsonwebtoken/impl/DefaultJwt.java | 5 +++++
src/main/java/io/jsonwebtoken/impl/JwtMap.java | 5 +++++
.../io/jsonwebtoken/impl/DefaultJwsTest.groovy | 13 +++++++++++++
.../io/jsonwebtoken/impl/DefaultJwtTest.groovy | 18 ++++++++++++++++++
5 files changed, 46 insertions(+)
create mode 100644 src/test/groovy/io/jsonwebtoken/impl/DefaultJwtTest.groovy
diff --git a/src/main/java/io/jsonwebtoken/impl/DefaultJws.java b/src/main/java/io/jsonwebtoken/impl/DefaultJws.java
index f1b4958b..fe83244c 100644
--- a/src/main/java/io/jsonwebtoken/impl/DefaultJws.java
+++ b/src/main/java/io/jsonwebtoken/impl/DefaultJws.java
@@ -44,4 +44,9 @@ public class DefaultJws implements Jws {
public String getSignature() {
return this.signature;
}
+
+ @Override
+ public String toString() {
+ return "header=" + header + ",body=" + body + ",signature=" + signature;
+ }
}
diff --git a/src/main/java/io/jsonwebtoken/impl/DefaultJwt.java b/src/main/java/io/jsonwebtoken/impl/DefaultJwt.java
index d5ed16c9..e09bd006 100644
--- a/src/main/java/io/jsonwebtoken/impl/DefaultJwt.java
+++ b/src/main/java/io/jsonwebtoken/impl/DefaultJwt.java
@@ -37,4 +37,9 @@ public class DefaultJwt implements Jwt {
public B getBody() {
return body;
}
+
+ @Override
+ public String toString() {
+ return "header=" + header + ",body=" + body;
+ }
}
diff --git a/src/main/java/io/jsonwebtoken/impl/JwtMap.java b/src/main/java/io/jsonwebtoken/impl/JwtMap.java
index 7c2de6d4..40d21224 100644
--- a/src/main/java/io/jsonwebtoken/impl/JwtMap.java
+++ b/src/main/java/io/jsonwebtoken/impl/JwtMap.java
@@ -150,4 +150,9 @@ public class JwtMap implements Map {
public Set> entrySet() {
return map.entrySet();
}
+
+ @Override
+ public String toString() {
+ return map.toString();
+ }
}
diff --git a/src/test/groovy/io/jsonwebtoken/impl/DefaultJwsTest.groovy b/src/test/groovy/io/jsonwebtoken/impl/DefaultJwsTest.groovy
index 3acaf314..33f0d97a 100644
--- a/src/test/groovy/io/jsonwebtoken/impl/DefaultJwsTest.groovy
+++ b/src/test/groovy/io/jsonwebtoken/impl/DefaultJwsTest.groovy
@@ -17,6 +17,8 @@ package io.jsonwebtoken.impl
import io.jsonwebtoken.JwsHeader
import io.jsonwebtoken.Jwts
+import io.jsonwebtoken.SignatureAlgorithm
+import io.jsonwebtoken.impl.crypto.MacProvider
import org.junit.Test
import static org.junit.Assert.*
@@ -32,4 +34,15 @@ class DefaultJwsTest {
assertEquals jws.getBody(), 'foo'
assertEquals jws.getSignature(), 'sig'
}
+
+ @Test
+ void testToString() {
+ //create random signing key for testing:
+ byte[] key = MacProvider.generateKey().encoded
+ String compact = Jwts.builder().claim('foo', 'bar').signWith(SignatureAlgorithm.HS256, key).compact();
+ int i = compact.lastIndexOf('.')
+ String signature = compact.substring(i + 1)
+ def jws = Jwts.parser().setSigningKey(key).parseClaimsJws(compact)
+ assertEquals 'header={alg=HS256},body={foo=bar},signature=' + signature, jws.toString()
+ }
}
diff --git a/src/test/groovy/io/jsonwebtoken/impl/DefaultJwtTest.groovy b/src/test/groovy/io/jsonwebtoken/impl/DefaultJwtTest.groovy
new file mode 100644
index 00000000..e916e509
--- /dev/null
+++ b/src/test/groovy/io/jsonwebtoken/impl/DefaultJwtTest.groovy
@@ -0,0 +1,18 @@
+package io.jsonwebtoken.impl
+
+import io.jsonwebtoken.Jwt
+import io.jsonwebtoken.Jwts
+import org.junit.Test
+
+import static org.junit.Assert.assertEquals
+
+class DefaultJwtTest {
+
+ @Test
+ void testToString() {
+ String compact = Jwts.builder().setHeaderParam('foo', 'bar').setAudience('jsmith').compact();
+ Jwt jwt = Jwts.parser().parseClaimsJwt(compact);
+ assertEquals 'header={foo=bar, alg=none},body={aud=jsmith}', jwt.toString()
+ }
+
+}
From 76de67fe5d3b9de7e1022732dbe72960cab00d35 Mon Sep 17 00:00:00 2001
From: Les Hazlewood
Date: Mon, 12 Oct 2015 16:02:15 -0700
Subject: [PATCH 32/50] updating readme to reflect 0.6 release features
---
README.md | 46 ++++++++++++++++++++++++++++++++++++++++++++--
1 file changed, 44 insertions(+), 2 deletions(-)
diff --git a/README.md b/README.md
index 655a333f..e0939f2a 100644
--- a/README.md
+++ b/README.md
@@ -101,9 +101,9 @@ These feature sets will be implemented in a future release when possible. Commu
### 0.6
-#### Enforce JWT Values when Parsing
+#### Enforce JWT Claims when Parsing
-We added the ability to set expectations when parsing a JWT which ensures particular claims having particular values.
+You can now enforce that JWT claims have expected values when parsing a compact JWT string.
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:
@@ -139,6 +139,48 @@ try {
```
(or, again, you could catch either MissingClaimException or IncorrectClaimException instead)
+#### Body Compression
+
+*This feature is NOT JWT specification compliant, but it can be very useful when you parse your own tokens.*
+
+If your JWT body is large and you have size restrictions (for example, if embedding a JWT in a URL and the URL must be under a certain length for legacy browsers or mail user agents), you may now compress the JWT body using a `CompressionCodec`:
+
+```java
+Jwts.builder().claim("foo", "someReallyDataString...")
+ .compressWith(CompressionCodecs.DEFLATE) // or CompressionCodecs.GZIP
+ .signWith(SignatureAlgorithm.HS256, key)
+ .compact();
+```
+
+This will set a new `calg` header with the name of the compression algorithm used so that parsers can see that value and decompress accordingly.
+
+The default parser implementation will automatically decompress DEFLATE or GZIP compressed bodies, so you don't need to set anything on the parser - it looks like normal:
+
+```java
+Jwts.parser().setSigningKey(key).parseClaimsJws(compact);
+```
+
+##### Custom Compression Algorithms
+
+If the DEFLATE or GZIP algorithms are not sufficient for your needs, you can specify your own Compression algorithms by implementing the `CompressionCodec` interface and setting it on the parser:
+
+```java
+Jwts.builder().claim("foo", "someReallyDataString...")
+ .compressWith(new MyCompressionCodec())
+ .signWith(SignatureAlgorithm.HS256, key)
+ .compact();
+```
+
+You will then need to specify a `CompressionCodecResolver` on the parser, so you can inspect the `calg` header and return your custom codec when discovered:
+
+```java
+Jwts.parser().setSigningKey(key)
+ .setCompressionCodecResolver(new MyCustomCompressionCodecResolver())
+ .parseClaimsJws(compact);
+```
+
+*NOTE*: Because body compression is not a standard JWT feature, you should only enable compression if both your JWT builder and parser are JJWT versions >= 0.6.0, or if you're using another library that implements the exact same functionality. It is best used for your own use cases - where you both create and later parse the tokens. This feature will likely cause problems if you compressed a token and expected a 3rd party (who doesn't use JJWT) to parse the token.
+
### 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.
From dad6dcf0f223873a9c73b637f401699ed1a63915 Mon Sep 17 00:00:00 2001
From: Les Hazlewood
Date: Mon, 12 Oct 2015 16:05:09 -0700
Subject: [PATCH 33/50] minor formatting change
---
README.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/README.md b/README.md
index e0939f2a..6b91d384 100644
--- a/README.md
+++ b/README.md
@@ -141,7 +141,7 @@ try {
#### Body Compression
-*This feature is NOT JWT specification compliant, but it can be very useful when you parse your own tokens.*
+**This feature is NOT JWT specification compliant*, but it can be very useful when you parse your own tokens.*
If your JWT body is large and you have size restrictions (for example, if embedding a JWT in a URL and the URL must be under a certain length for legacy browsers or mail user agents), you may now compress the JWT body using a `CompressionCodec`:
From 6a422211c8a16013592cbd88d0346ebe4ca3e788 Mon Sep 17 00:00:00 2001
From: Les Hazlewood
Date: Mon, 12 Oct 2015 16:07:01 -0700
Subject: [PATCH 34/50] Update README.md
---
README.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/README.md b/README.md
index 6b91d384..249f6975 100644
--- a/README.md
+++ b/README.md
@@ -141,7 +141,7 @@ try {
#### Body Compression
-**This feature is NOT JWT specification compliant*, but it can be very useful when you parse your own tokens.*
+**This feature is NOT JWT specification compliant**, *but it can be very useful when you parse your own tokens*.
If your JWT body is large and you have size restrictions (for example, if embedding a JWT in a URL and the URL must be under a certain length for legacy browsers or mail user agents), you may now compress the JWT body using a `CompressionCodec`:
From 65f9b02de3007ec55f441c45331e413650211382 Mon Sep 17 00:00:00 2001
From: Les Hazlewood
Date: Mon, 12 Oct 2015 16:07:33 -0700
Subject: [PATCH 35/50] Update README.md
---
README.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/README.md b/README.md
index 249f6975..7193a226 100644
--- a/README.md
+++ b/README.md
@@ -141,7 +141,7 @@ try {
#### Body Compression
-**This feature is NOT JWT specification compliant**, *but it can be very useful when you parse your own tokens*.
+***This feature is NOT JWT specification compliant**, but it can be very useful when you parse your own tokens*.
If your JWT body is large and you have size restrictions (for example, if embedding a JWT in a URL and the URL must be under a certain length for legacy browsers or mail user agents), you may now compress the JWT body using a `CompressionCodec`:
From 1649066038453d9e412698bbb81db77ef9495889 Mon Sep 17 00:00:00 2001
From: Les Hazlewood
Date: Mon, 12 Oct 2015 16:08:06 -0700
Subject: [PATCH 36/50] Update README.md
---
README.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/README.md b/README.md
index 7193a226..249f6975 100644
--- a/README.md
+++ b/README.md
@@ -141,7 +141,7 @@ try {
#### Body Compression
-***This feature is NOT JWT specification compliant**, but it can be very useful when you parse your own tokens*.
+**This feature is NOT JWT specification compliant**, *but it can be very useful when you parse your own tokens*.
If your JWT body is large and you have size restrictions (for example, if embedding a JWT in a URL and the URL must be under a certain length for legacy browsers or mail user agents), you may now compress the JWT body using a `CompressionCodec`:
From e4e37373b84d61322e83c075a3b657929dc1b8bb Mon Sep 17 00:00:00 2001
From: Les Hazlewood
Date: Mon, 12 Oct 2015 16:12:43 -0700
Subject: [PATCH 37/50] Update README.md
---
README.md | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/README.md b/README.md
index 249f6975..6da4f317 100644
--- a/README.md
+++ b/README.md
@@ -146,7 +146,7 @@ try {
If your JWT body is large and you have size restrictions (for example, if embedding a JWT in a URL and the URL must be under a certain length for legacy browsers or mail user agents), you may now compress the JWT body using a `CompressionCodec`:
```java
-Jwts.builder().claim("foo", "someReallyDataString...")
+Jwts.builder().claim("foo", "someReallyLongDataString...")
.compressWith(CompressionCodecs.DEFLATE) // or CompressionCodecs.GZIP
.signWith(SignatureAlgorithm.HS256, key)
.compact();
@@ -165,7 +165,7 @@ Jwts.parser().setSigningKey(key).parseClaimsJws(compact);
If the DEFLATE or GZIP algorithms are not sufficient for your needs, you can specify your own Compression algorithms by implementing the `CompressionCodec` interface and setting it on the parser:
```java
-Jwts.builder().claim("foo", "someReallyDataString...")
+Jwts.builder().claim("foo", "someReallyLongDataString...")
.compressWith(new MyCompressionCodec())
.signWith(SignatureAlgorithm.HS256, key)
.compact();
@@ -179,7 +179,7 @@ Jwts.parser().setSigningKey(key)
.parseClaimsJws(compact);
```
-*NOTE*: Because body compression is not a standard JWT feature, you should only enable compression if both your JWT builder and parser are JJWT versions >= 0.6.0, or if you're using another library that implements the exact same functionality. It is best used for your own use cases - where you both create and later parse the tokens. This feature will likely cause problems if you compressed a token and expected a 3rd party (who doesn't use JJWT) to parse the token.
+*NOTE*: Because body compression is not a standard JWT feature, you should only enable compression if both your JWT builder and parser are JJWT versions >= 0.6.0, or if you're using another library that implements the exact same functionality. This feature is best reserved for your own use cases - where you both create and later parse the tokens. It will likely cause problems if you compressed a token and expected a 3rd party (who doesn't use JJWT) to parse the token.
### 0.5.1
From efe20ee14b08490e287f05ba42e0fc23e9d84427 Mon Sep 17 00:00:00 2001
From: Les Hazlewood
Date: Mon, 12 Oct 2015 16:15:06 -0700
Subject: [PATCH 38/50] Update README.md
---
README.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/README.md b/README.md
index 6da4f317..fa8b3db3 100644
--- a/README.md
+++ b/README.md
@@ -179,7 +179,7 @@ Jwts.parser().setSigningKey(key)
.parseClaimsJws(compact);
```
-*NOTE*: Because body compression is not a standard JWT feature, you should only enable compression if both your JWT builder and parser are JJWT versions >= 0.6.0, or if you're using another library that implements the exact same functionality. This feature is best reserved for your own use cases - where you both create and later parse the tokens. It will likely cause problems if you compressed a token and expected a 3rd party (who doesn't use JJWT) to parse the token.
+*NOTE*: Because body compression is not JWT specification compliant, you should only enable compression if both your JWT builder and parser are JJWT versions >= 0.6.0, or if you're using another library that implements the exact same functionality. This feature is best reserved for your own use cases - where you both create and later parse the tokens. It will likely cause problems if you compressed a token and expected a 3rd party (who doesn't use JJWT) to parse the token.
### 0.5.1
From 267bc09f6a4e053deff5e3677d669726bb109803 Mon Sep 17 00:00:00 2001
From: Les Hazlewood
Date: Mon, 12 Oct 2015 16:19:30 -0700
Subject: [PATCH 39/50] Changing the version from 0.5.2-SNAPSHOT to
0.6.0-SNAPSHOT in preparation for release
---
pom.xml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/pom.xml b/pom.xml
index 057dea65..23c2ae47 100644
--- a/pom.xml
+++ b/pom.xml
@@ -25,7 +25,7 @@
io.jsonwebtoken
jjwt
- 0.5.2-SNAPSHOT
+ 0.6.0-SNAPSHOT
JSON Web Token support for the JVM
jar
From a4f4da767be3d9fc988a6a2fe961276d28dbdb8c Mon Sep 17 00:00:00 2001
From: Les Hazlewood
Date: Mon, 12 Oct 2015 16:22:44 -0700
Subject: [PATCH 40/50] Update README.md
Updated version references to reflect concrete version number per the release
---
README.md | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/README.md b/README.md
index fa8b3db3..e9ef5b20 100644
--- a/README.md
+++ b/README.md
@@ -16,7 +16,7 @@ Maven:
io.jsonwebtoken
jjwt
- 0.6
+ 0.6.0
```
@@ -24,7 +24,7 @@ Gradle:
```groovy
dependencies {
- compile 'io.jsonwebtoken:jjwt:0.6'
+ compile 'io.jsonwebtoken:jjwt:0.6.0'
}
```
@@ -99,7 +99,7 @@ These feature sets will be implemented in a future release when possible. Commu
## Release Notes
-### 0.6
+### 0.6.0
#### Enforce JWT Claims when Parsing
From 98970a7e19de96c45a736a89922b9971d1fbc819 Mon Sep 17 00:00:00 2001
From: Les Hazlewood
Date: Mon, 12 Oct 2015 16:23:21 -0700
Subject: [PATCH 41/50] Changed version references from 0.5.2 to 0.6.0 (no
0.5.2 release yet).
---
src/main/java/io/jsonwebtoken/CompressionCodec.java | 2 +-
src/main/java/io/jsonwebtoken/CompressionCodecResolver.java | 2 +-
src/main/java/io/jsonwebtoken/CompressionException.java | 2 +-
src/main/java/io/jsonwebtoken/Header.java | 4 ++--
src/main/java/io/jsonwebtoken/JwtBuilder.java | 6 +++---
src/main/java/io/jsonwebtoken/JwtParser.java | 6 +++---
.../impl/compression/AbstractCompressionCodec.java | 2 +-
.../io/jsonwebtoken/impl/compression/CompressionCodecs.java | 2 +-
.../impl/compression/DefaultCompressionCodecResolver.java | 2 +-
.../impl/compression/DeflateCompressionCodec.java | 2 +-
.../jsonwebtoken/impl/compression/GzipCompressionCodec.java | 2 +-
.../impl/compression/AbstractCompressionCodecTest.groovy | 2 +-
12 files changed, 17 insertions(+), 17 deletions(-)
diff --git a/src/main/java/io/jsonwebtoken/CompressionCodec.java b/src/main/java/io/jsonwebtoken/CompressionCodec.java
index 12264f22..b1b3dd6c 100644
--- a/src/main/java/io/jsonwebtoken/CompressionCodec.java
+++ b/src/main/java/io/jsonwebtoken/CompressionCodec.java
@@ -20,7 +20,7 @@ package io.jsonwebtoken;
*
* @see io.jsonwebtoken.impl.compression.DeflateCompressionCodec
* @see io.jsonwebtoken.impl.compression.GzipCompressionCodec
- * @since 0.5.2
+ * @since 0.6.0
*/
public interface CompressionCodec {
diff --git a/src/main/java/io/jsonwebtoken/CompressionCodecResolver.java b/src/main/java/io/jsonwebtoken/CompressionCodecResolver.java
index 54d3c7fc..afb2e82a 100644
--- a/src/main/java/io/jsonwebtoken/CompressionCodecResolver.java
+++ b/src/main/java/io/jsonwebtoken/CompressionCodecResolver.java
@@ -29,7 +29,7 @@ package io.jsonwebtoken;
* {@link io.jsonwebtoken.JwtBuilder#compressWith(CompressionCodec) building} and
* {@link io.jsonwebtoken.JwtParser#setCompressionCodecResolver(CompressionCodecResolver) parsing} JWTs.
*
- * @since 0.5.2
+ * @since 0.6.0
*/
public interface CompressionCodecResolver {
diff --git a/src/main/java/io/jsonwebtoken/CompressionException.java b/src/main/java/io/jsonwebtoken/CompressionException.java
index d4ac62d3..287ccfb0 100644
--- a/src/main/java/io/jsonwebtoken/CompressionException.java
+++ b/src/main/java/io/jsonwebtoken/CompressionException.java
@@ -18,7 +18,7 @@ package io.jsonwebtoken;
/**
* Exception indicating that either compressing or decompressing an JWT body failed.
*
- * @since 0.5.2
+ * @since 0.6.0
*/
public class CompressionException extends JwtException {
diff --git a/src/main/java/io/jsonwebtoken/Header.java b/src/main/java/io/jsonwebtoken/Header.java
index 81068c73..840d26de 100644
--- a/src/main/java/io/jsonwebtoken/Header.java
+++ b/src/main/java/io/jsonwebtoken/Header.java
@@ -107,7 +107,7 @@ public interface Header> extends Map {
* Returns the JWT calg
(Compression Algorithm) header value or {@code null} if not present.
*
* @return the {@code calg} header parameter value or {@code null} if not present.
- * @since 0.5.2
+ * @since 0.6.0
*/
String getCompressionAlgorithm();
@@ -120,7 +120,7 @@ public interface Header> extends Map {
* be able to deserialize a compressed JTW body correctly.
*
* @param calg the JWT compression algorithm {@code calg} value or {@code null} to remove the property from the JSON map.
- * @since 0.5.2
+ * @since 0.6.0
*/
T setCompressionAlgorithm(String calg);
diff --git a/src/main/java/io/jsonwebtoken/JwtBuilder.java b/src/main/java/io/jsonwebtoken/JwtBuilder.java
index f88d2b0f..df68e9f6 100644
--- a/src/main/java/io/jsonwebtoken/JwtBuilder.java
+++ b/src/main/java/io/jsonwebtoken/JwtBuilder.java
@@ -357,15 +357,15 @@ public interface JwtBuilder extends ClaimsMutator {
* certain length. Using compression can help ensure the compact JWT fits within that length. However, NOTE:
*
* WARNING: Compression is not defined by the JWT Specification, and it is not expected that other libraries
- * (including JJWT versions < 0.5.2) are able to consume a compressed JWT body correctly. Only use this method
- * if you are sure that you will consume the JWT with JJWT >= 0.5.2 or another library that you know implements
+ * (including JJWT versions < 0.6.0) are able to consume a compressed JWT body correctly. Only use this method
+ * if you are sure that you will consume the JWT with JJWT >= 0.6.0 or another library that you know implements
* the same behavior.
*
* @see io.jsonwebtoken.impl.compression.CompressionCodecs
*
* @param codec implementation of the {@link CompressionCodec} to be used.
* @return the builder for method chaining.
- * @since 0.5.2
+ * @since 0.6.0
*/
JwtBuilder compressWith(CompressionCodec codec);
diff --git a/src/main/java/io/jsonwebtoken/JwtParser.java b/src/main/java/io/jsonwebtoken/JwtParser.java
index 88daf131..11e8a5f6 100644
--- a/src/main/java/io/jsonwebtoken/JwtParser.java
+++ b/src/main/java/io/jsonwebtoken/JwtParser.java
@@ -209,8 +209,8 @@ public interface JwtParser {
* Sets the {@link CompressionCodecResolver} used to acquire the {@link CompressionCodec} that should be used to
* decompress the JWT body. If the parsed JWT is not compressed, this resolver is not used.
* NOTE: Compression is not defined by the JWT Specification, and it is not expected that other libraries
- * (including JJWT versions < 0.5.2) are able to consume a compressed JWT body correctly. This method is only
- * useful if the compact JWT was compressed with JJWT >= 0.5.2 or another library that you know implements
+ * (including JJWT versions < 0.6.0) are able to consume a compressed JWT body correctly. This method is only
+ * useful if the compact JWT was compressed with JJWT >= 0.6.0 or another library that you know implements
* the same behavior.
* Default Support
* JJWT's default {@link JwtParser} implementation supports both the
@@ -223,7 +223,7 @@ public interface JwtParser {
*
* @param compressionCodecResolver the compression codec resolver used to decompress the JWT body.
* @return the parser for method chaining.
- * @since 0.5.2
+ * @since 0.6.0
*/
JwtParser setCompressionCodecResolver(CompressionCodecResolver compressionCodecResolver);
diff --git a/src/main/java/io/jsonwebtoken/impl/compression/AbstractCompressionCodec.java b/src/main/java/io/jsonwebtoken/impl/compression/AbstractCompressionCodec.java
index 9457b68b..e1b2e653 100644
--- a/src/main/java/io/jsonwebtoken/impl/compression/AbstractCompressionCodec.java
+++ b/src/main/java/io/jsonwebtoken/impl/compression/AbstractCompressionCodec.java
@@ -24,7 +24,7 @@ import java.io.IOException;
/**
* Abstract class that asserts arguments and wraps IOException with CompressionException.
*
- * @since 0.5.2
+ * @since 0.6.0
*/
public abstract class AbstractCompressionCodec implements CompressionCodec {
diff --git a/src/main/java/io/jsonwebtoken/impl/compression/CompressionCodecs.java b/src/main/java/io/jsonwebtoken/impl/compression/CompressionCodecs.java
index f81721cd..19a5f293 100644
--- a/src/main/java/io/jsonwebtoken/impl/compression/CompressionCodecs.java
+++ b/src/main/java/io/jsonwebtoken/impl/compression/CompressionCodecs.java
@@ -23,7 +23,7 @@ import io.jsonwebtoken.CompressionCodec;
* @see #DEFLATE
* @see #GZIP
*
- * @since 0.5.2
+ * @since 0.6.0
*/
public final class CompressionCodecs {
diff --git a/src/main/java/io/jsonwebtoken/impl/compression/DefaultCompressionCodecResolver.java b/src/main/java/io/jsonwebtoken/impl/compression/DefaultCompressionCodecResolver.java
index 159a6f44..fb32c1c6 100644
--- a/src/main/java/io/jsonwebtoken/impl/compression/DefaultCompressionCodecResolver.java
+++ b/src/main/java/io/jsonwebtoken/impl/compression/DefaultCompressionCodecResolver.java
@@ -41,7 +41,7 @@ import io.jsonwebtoken.lang.Strings;
*
* @see DeflateCompressionCodec
* @see GzipCompressionCodec
- * @since 0.5.2
+ * @since 0.6.0
*/
public class DefaultCompressionCodecResolver implements CompressionCodecResolver {
diff --git a/src/main/java/io/jsonwebtoken/impl/compression/DeflateCompressionCodec.java b/src/main/java/io/jsonwebtoken/impl/compression/DeflateCompressionCodec.java
index 90ee9dbc..9f76970b 100644
--- a/src/main/java/io/jsonwebtoken/impl/compression/DeflateCompressionCodec.java
+++ b/src/main/java/io/jsonwebtoken/impl/compression/DeflateCompressionCodec.java
@@ -26,7 +26,7 @@ import java.util.zip.InflaterOutputStream;
/**
* Codec implementing the deflate compression algorithm.
*
- * @since 0.5.2
+ * @since 0.6.0
*/
public class DeflateCompressionCodec extends AbstractCompressionCodec {
diff --git a/src/main/java/io/jsonwebtoken/impl/compression/GzipCompressionCodec.java b/src/main/java/io/jsonwebtoken/impl/compression/GzipCompressionCodec.java
index f15531e1..19bf7e20 100644
--- a/src/main/java/io/jsonwebtoken/impl/compression/GzipCompressionCodec.java
+++ b/src/main/java/io/jsonwebtoken/impl/compression/GzipCompressionCodec.java
@@ -27,7 +27,7 @@ import java.util.zip.GZIPOutputStream;
/**
* Codec implementing the gzip compression algorithm.
*
- * @since 0.5.2
+ * @since 0.6.0
*/
public class GzipCompressionCodec extends AbstractCompressionCodec implements CompressionCodec {
diff --git a/src/test/groovy/io/jsonwebtoken/impl/compression/AbstractCompressionCodecTest.groovy b/src/test/groovy/io/jsonwebtoken/impl/compression/AbstractCompressionCodecTest.groovy
index dc497c37..a1e17436 100644
--- a/src/test/groovy/io/jsonwebtoken/impl/compression/AbstractCompressionCodecTest.groovy
+++ b/src/test/groovy/io/jsonwebtoken/impl/compression/AbstractCompressionCodecTest.groovy
@@ -20,7 +20,7 @@ import io.jsonwebtoken.CompressionException
import org.junit.Test
/**
- * @since 0.5.2
+ * @since 0.6.0
*/
class AbstractCompressionCodecTest {
static class ExceptionThrowingCodec extends AbstractCompressionCodec {
From 8b3f6ab49619de5b164719e660f09a88f3919b93 Mon Sep 17 00:00:00 2001
From: Les Hazlewood
Date: Wed, 14 Oct 2015 13:50:30 -0700
Subject: [PATCH 42/50] [maven-release-plugin] prepare release 0.6.0
---
pom.xml | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/pom.xml b/pom.xml
index 23c2ae47..20e8adb2 100644
--- a/pom.xml
+++ b/pom.xml
@@ -25,7 +25,7 @@
io.jsonwebtoken
jjwt
- 0.6.0-SNAPSHOT
+ 0.6.0
JSON Web Token support for the JVM
jar
@@ -41,7 +41,7 @@
scm:git:https://github.com/jwtk/jjwt.git
scm:git:git@github.com:jwtk/jjwt.git
git@github.com:jwtk/jjwt.git
- HEAD
+ 0.6.0
GitHub Issues
From 44b652777bca8f28fb117f94c1d6081c11b9fa75 Mon Sep 17 00:00:00 2001
From: Les Hazlewood
Date: Wed, 14 Oct 2015 13:50:34 -0700
Subject: [PATCH 43/50] [maven-release-plugin] prepare for next development
iteration
---
pom.xml | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/pom.xml b/pom.xml
index 20e8adb2..7a4e565b 100644
--- a/pom.xml
+++ b/pom.xml
@@ -25,7 +25,7 @@
io.jsonwebtoken
jjwt
- 0.6.0
+ 0.7.0-SNAPSHOT
JSON Web Token support for the JVM
jar
@@ -41,7 +41,7 @@
scm:git:https://github.com/jwtk/jjwt.git
scm:git:git@github.com:jwtk/jjwt.git
git@github.com:jwtk/jjwt.git
- 0.6.0
+ HEAD
GitHub Issues
From 687fe6a7371434fd3975c8e7a39ea0beb3881561 Mon Sep 17 00:00:00 2001
From: Micah Silverman
Date: Tue, 27 Oct 2015 21:55:52 -0400
Subject: [PATCH 44/50] Added coveralls coverage report.
---
README.md | 1 +
1 file changed, 1 insertion(+)
diff --git a/README.md b/README.md
index e9ef5b20..0808a9f0 100644
--- a/README.md
+++ b/README.md
@@ -1,4 +1,5 @@
[![Build Status](https://travis-ci.org/jwtk/jjwt.svg?branch=master)](https://travis-ci.org/jwtk/jjwt)
+[![Coverage Status](https://coveralls.io/repos/jwtk/jjwt/badge.svg?branch=master)](https://coveralls.io/r/jwtk/jjwt?branch=master)
# Java JWT: JSON Web Token for Java and Android
From 1d9fd734c9ceb3a5106e59286bfda1959a1a4e5c Mon Sep 17 00:00:00 2001
From: Micah Silverman
Date: Tue, 27 Oct 2015 22:01:36 -0400
Subject: [PATCH 45/50] Added coveralls maven plugin.
---
pom.xml | 10 +++++++++-
1 file changed, 9 insertions(+), 1 deletion(-)
diff --git a/pom.xml b/pom.xml
index 7a4e565b..5e84ff3d 100644
--- a/pom.xml
+++ b/pom.xml
@@ -274,6 +274,8 @@
cobertura-maven-plugin
2.7
+ 256m
+ true
io/jsonwebtoken/lang/*.class
@@ -319,6 +321,7 @@
+ xml
html
@@ -364,6 +367,11 @@
+
+ org.eluder.coveralls
+ coveralls-maven-plugin
+ 4.0.0
+
@@ -434,4 +442,4 @@
-
\ No newline at end of file
+
From 4773224c744bc461b699dc713e4995e6cf8985b8 Mon Sep 17 00:00:00 2001
From: Micah Silverman
Date: Tue, 27 Oct 2015 22:09:02 -0400
Subject: [PATCH 46/50] Added code to build coverage report to .travis.yml
---
.travis.yml | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/.travis.yml b/.travis.yml
index 297c5d7c..e7ab1c34 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -6,5 +6,9 @@ jdk:
- oraclejdk7
- oraclejdk8
+before_install:
+ - export BUILD_COVERAGE="$([ $TRAVIS_JDK_VERSION == 'openjdk7' ] && echo 'true')"
install: echo "No need to run mvn install -DskipTests then mvn install. Running mvn install."
script: mvn install
+after_success:
+ - test -z "$BUILD_COVERAGE" || mvn clean cobertura:cobertura coveralls:report
From 7843179ad5e38ee20d41e307925b54fe880e19e5 Mon Sep 17 00:00:00 2001
From: Micah Silverman
Date: Tue, 27 Oct 2015 23:27:18 -0400
Subject: [PATCH 47/50] Improve coverage on compact by exercising
JsonProcessingException.
---
.../impl/DefaultJwtBuilderTest.groovy | 21 +++++++++++++++++++
1 file changed, 21 insertions(+)
diff --git a/src/test/groovy/io/jsonwebtoken/impl/DefaultJwtBuilderTest.groovy b/src/test/groovy/io/jsonwebtoken/impl/DefaultJwtBuilderTest.groovy
index 4ee7d8e5..ac316704 100644
--- a/src/test/groovy/io/jsonwebtoken/impl/DefaultJwtBuilderTest.groovy
+++ b/src/test/groovy/io/jsonwebtoken/impl/DefaultJwtBuilderTest.groovy
@@ -19,6 +19,7 @@ import com.fasterxml.jackson.core.JsonProcessingException
import com.fasterxml.jackson.databind.JsonMappingException
import io.jsonwebtoken.Jwts
import io.jsonwebtoken.SignatureAlgorithm
+import io.jsonwebtoken.impl.compression.CompressionCodecs
import io.jsonwebtoken.impl.crypto.MacProvider
import org.junit.Test
@@ -187,6 +188,26 @@ class DefaultJwtBuilderTest {
}
+ @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')
+ }
+ }
+
+ def c = Jwts.claims().setSubject("Joe");
+
+ try {
+ b.setClaims(c).compressWith(CompressionCodecs.DEFLATE).compact()
+ fail()
+ } catch (IllegalArgumentException iae) {
+ assertEquals iae.message, 'Unable to serialize claims object to json.'
+ }
+ }
+
@Test
void testSignWithBytesWithoutHmac() {
def bytes = new byte[16];
From 4020dfc1d51395f295b63bc60346839ccd4ff942 Mon Sep 17 00:00:00 2001
From: Les Hazlewood
Date: Sat, 21 Nov 2015 15:00:23 -0800
Subject: [PATCH 48/50] Ensures RSA Signatures can work on Android 23
---
.../java/io/jsonwebtoken/impl/crypto/RsaSigner.java | 10 ++++++----
.../io/jsonwebtoken/impl/crypto/RsaSignerTest.groovy | 4 ++--
2 files changed, 8 insertions(+), 6 deletions(-)
diff --git a/src/main/java/io/jsonwebtoken/impl/crypto/RsaSigner.java b/src/main/java/io/jsonwebtoken/impl/crypto/RsaSigner.java
index 3a3849df..087ac33f 100644
--- a/src/main/java/io/jsonwebtoken/impl/crypto/RsaSigner.java
+++ b/src/main/java/io/jsonwebtoken/impl/crypto/RsaSigner.java
@@ -22,15 +22,17 @@ import java.security.InvalidKeyException;
import java.security.Key;
import java.security.PrivateKey;
import java.security.Signature;
-import java.security.interfaces.RSAPrivateKey;
+import java.security.interfaces.RSAKey;
public class RsaSigner extends RsaProvider implements Signer {
public RsaSigner(SignatureAlgorithm alg, Key key) {
super(alg, key);
- if (!(key instanceof RSAPrivateKey)) {
- String msg = "RSA signatures must be computed using an RSAPrivateKey. The specified key of type " +
- key.getClass().getName() + " is not an RSAPrivateKey.";
+ // https://github.com/jwtk/jjwt/issues/68
+ // Instead of checking for an instance of RSAPrivateKey, check for PrivateKey and RSAKey:
+ if (!(key instanceof PrivateKey && key instanceof RSAKey)) {
+ String msg = "RSA signatures must be computed using an RSA PrivateKey. The specified key of type " +
+ key.getClass().getName() + " is not an RSA PrivateKey.";
throw new IllegalArgumentException(msg);
}
}
diff --git a/src/test/groovy/io/jsonwebtoken/impl/crypto/RsaSignerTest.groovy b/src/test/groovy/io/jsonwebtoken/impl/crypto/RsaSignerTest.groovy
index 39fe04c0..78032b61 100644
--- a/src/test/groovy/io/jsonwebtoken/impl/crypto/RsaSignerTest.groovy
+++ b/src/test/groovy/io/jsonwebtoken/impl/crypto/RsaSignerTest.groovy
@@ -58,8 +58,8 @@ class RsaSignerTest {
new RsaSigner(SignatureAlgorithm.RS256, key);
fail('RsaSigner should reject non RSAPrivateKey instances.')
} catch (IllegalArgumentException expected) {
- assertEquals expected.message, "RSA signatures must be computed using an RSAPrivateKey. The specified key of type " +
- key.getClass().getName() + " is not an RSAPrivateKey.";
+ assertEquals expected.message, "RSA signatures must be computed using an RSA PrivateKey. The specified key of type " +
+ key.getClass().getName() + " is not an RSA PrivateKey.";
}
}
From 35954235769abc09a2a0711acfcb451f42ed1c9b Mon Sep 17 00:00:00 2001
From: Les Hazlewood
Date: Sat, 21 Nov 2015 15:16:42 -0800
Subject: [PATCH 49/50] #68: ensured branch code coverage
---
.../impl/crypto/RsaSignerTest.groovy | 55 ++++++++++++++++++-
1 file changed, 54 insertions(+), 1 deletion(-)
diff --git a/src/test/groovy/io/jsonwebtoken/impl/crypto/RsaSignerTest.groovy b/src/test/groovy/io/jsonwebtoken/impl/crypto/RsaSignerTest.groovy
index 78032b61..4b804442 100644
--- a/src/test/groovy/io/jsonwebtoken/impl/crypto/RsaSignerTest.groovy
+++ b/src/test/groovy/io/jsonwebtoken/impl/crypto/RsaSignerTest.groovy
@@ -22,6 +22,7 @@ import javax.crypto.spec.SecretKeySpec
import java.security.InvalidKeyException
import java.security.KeyPair
import java.security.KeyPairGenerator
+import java.security.MessageDigest
import java.security.PrivateKey
import java.security.PublicKey
@@ -48,13 +49,45 @@ class RsaSignerTest {
}
@Test
- void testConstructorWithoutRsaPrivateKey() {
+ void testConstructorWithoutPrivateKey() {
byte[] bytes = new byte[16]
rng.nextBytes(bytes)
SecretKeySpec key = new SecretKeySpec(bytes, 'HmacSHA256')
try {
+ //noinspection GroovyResultOfObjectAllocationIgnored
+ new RsaSigner(SignatureAlgorithm.RS256, key);
+ fail('RsaSigner should reject non RSAPrivateKey instances.')
+ } catch (IllegalArgumentException expected) {
+ assertEquals expected.message, "RSA signatures must be computed using an RSA PrivateKey. The specified key of type " +
+ key.getClass().getName() + " is not an RSA PrivateKey.";
+ }
+ }
+
+ @Test
+ void testConstructorWithoutRSAKey() {
+
+ //private key, but not an RSAKey instance:
+ PrivateKey key = new PrivateKey() {
+ @Override
+ String getAlgorithm() {
+ return null
+ }
+
+ @Override
+ String getFormat() {
+ return null
+ }
+
+ @Override
+ byte[] getEncoded() {
+ return new byte[0]
+ }
+ }
+
+ try {
+ //noinspection GroovyResultOfObjectAllocationIgnored
new RsaSigner(SignatureAlgorithm.RS256, key);
fail('RsaSigner should reject non RSAPrivateKey instances.')
} catch (IllegalArgumentException expected) {
@@ -126,4 +159,24 @@ class RsaSignerTest {
assertSame se.cause, ex
}
}
+
+ @Test
+ void testSignSuccessful() {
+
+ KeyPairGenerator keyGenerator = KeyPairGenerator.getInstance("RSA");
+ keyGenerator.initialize(1024);
+
+ KeyPair kp = keyGenerator.genKeyPair();
+ PrivateKey privateKey = kp.getPrivate();
+
+ byte[] bytes = new byte[16]
+ rng.nextBytes(bytes)
+
+ RsaSigner signer = new RsaSigner(SignatureAlgorithm.RS256, privateKey);
+ byte[] out1 = signer.sign(bytes)
+
+ byte[] out2 = signer.sign(bytes)
+
+ assertTrue(MessageDigest.isEqual(out1, out2))
+ }
}
From 638d84963f79280fa74ff876def58fc63ed1e609 Mon Sep 17 00:00:00 2001
From: Les Hazlewood
Date: Fri, 11 Dec 2015 09:48:50 -0800
Subject: [PATCH 50/50] Updated spec links to final RFC documents
---
README.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/README.md b/README.md
index 0808a9f0..b3ea7e01 100644
--- a/README.md
+++ b/README.md
@@ -5,7 +5,7 @@
JJWT aims to be the easiest to use and understand library for creating and verifying JSON Web Tokens (JWTs) on the JVM.
-JJWT is a 'clean room' implementation based solely on the [JWT](https://tools.ietf.org/html/draft-ietf-oauth-json-web-token-25), [JWS](https://tools.ietf.org/html/draft-ietf-jose-json-web-signature-31), [JWE](https://tools.ietf.org/html/draft-ietf-jose-json-web-encryption-31) and [JWA](https://tools.ietf.org/html/draft-ietf-jose-json-web-algorithms-31) RFC draft specifications.
+JJWT is a 'clean room' implementation based solely on the [JWT](https://tools.ietf.org/html/rfc7519), [JWS](https://tools.ietf.org/html/rfc7515), [JWE](https://tools.ietf.org/html/rfc7516), [JWK](https://tools.ietf.org/html/rfc7517) and [JWA](https://tools.ietf.org/html/rfc7518) RFC specifications.
## Installation