Skip to content

Commit

Permalink
Issue #6: Fixed tests to not reference static (stale) expiration dates
Browse files Browse the repository at this point in the history
  • Loading branch information
lhazlewood committed Oct 29, 2014
1 parent 076fa30 commit 35a4282
Show file tree
Hide file tree
Showing 4 changed files with 81 additions and 25 deletions.
2 changes: 1 addition & 1 deletion src/main/java/io/jsonwebtoken/ExpiredJwtException.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
package io.jsonwebtoken;

/**
* Exception indicating that a JWT was referenced after it expired and should be rejected.
* Exception indicating that a JWT was accepted after it expired and must be rejected.
*
* @since 0.3
*/
Expand Down
56 changes: 42 additions & 14 deletions src/main/java/io/jsonwebtoken/impl/DefaultJwtParser.java
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,8 @@
import io.jsonwebtoken.ExpiredJwtException;
import io.jsonwebtoken.Header;
import io.jsonwebtoken.Jws;
import io.jsonwebtoken.Jwt;
import io.jsonwebtoken.JwsHeader;
import io.jsonwebtoken.Jwt;
import io.jsonwebtoken.JwtHandler;
import io.jsonwebtoken.JwtHandlerAdapter;
import io.jsonwebtoken.JwtParser;
Expand All @@ -45,6 +45,9 @@
@SuppressWarnings("unchecked")
public class DefaultJwtParser implements JwtParser {

//don't need millis since JWT date fields are only second granularity:
private static final String ISO_8601_FORMAT = "yyyy-MM-dd'T'HH:mm:ssZ";

private ObjectMapper objectMapper = new ObjectMapper();

private byte[] keyBytes;
Expand Down Expand Up @@ -167,29 +170,52 @@ public Jwt parse(String jwt) throws ExpiredJwtException, MalformedJwtException,
//since 0.3:
if (claims != null) {

Date exp = claims.getExpiration();
Date now = null;
SimpleDateFormat sdf;

//https://tools.ietf.org/html/draft-ietf-oauth-json-web-token-30#section-4.1.4
//token MUST NOT be accepted on or after any specified exp time:
Date exp = claims.getExpiration();
if (exp != null) {

Date now = new Date();

if (now.after(exp)) {

SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ"); //don't need millis since JWT exp field only has second granularity
now = new Date();

if (now.equals(exp) || now.after(exp)) {
sdf = new SimpleDateFormat(ISO_8601_FORMAT);
String expVal = sdf.format(exp);
String nowVal = sdf.format(now);

String msg = "JWT expired at " + expVal + ". Current time: " + nowVal;
throw new ExpiredJwtException(msg);
}
}

/*
//https://tools.ietf.org/html/draft-ietf-oauth-json-web-token-30#section-4.1.5
//token MUST NOT be accepted before any specified nbf time:
Date nbf = claims.getNotBefore();
if (nbf != null) {
if (now == null) {
now = new Date();
}
if (now.before(nbf)) {
sdf = new SimpleDateFormat(ISO_8601_FORMAT);
String nbfVal = sdf.format(nbf);
String nowVal = sdf.format(now);
String msg = "JWT must not be accepted before " + nbfVal + ". Current time: " + nowVal;
throw new PrematureJwtException(msg);
}
}
*/
}

// =============== Signature =================
if (base64UrlEncodedDigest != null) { //it is signed - validate the signature

JwsHeader jwsHeader = (JwsHeader)header;
JwsHeader jwsHeader = (JwsHeader) header;

SignatureAlgorithm algorithm = null;

Expand Down Expand Up @@ -218,7 +244,8 @@ public Jwt parse(String jwt) throws ExpiredJwtException, MalformedJwtException,

if (!Objects.isEmpty(this.keyBytes)) {

Assert.isTrue(!algorithm.isRsa(), "Key bytes cannot be specified for RSA signatures. Please specify a PublicKey or PrivateKey instance.");
Assert.isTrue(!algorithm.isRsa(),
"Key bytes cannot be specified for RSA signatures. Please specify a PublicKey or PrivateKey instance.");

key = new SecretKeySpec(keyBytes, algorithm.getJcaName());
}
Expand All @@ -241,26 +268,27 @@ public Jwt parse(String jwt) throws ExpiredJwtException, MalformedJwtException,
Object body = claims != null ? claims : payload;

if (base64UrlEncodedDigest != null) {
return new DefaultJws<Object>((JwsHeader)header, body, base64UrlEncodedDigest);
return new DefaultJws<Object>((JwsHeader) header, body, base64UrlEncodedDigest);
} else {
return new DefaultJwt<Object>(header, body);
}
}

@Override
public <T> T parse(String compact, JwtHandler<T> handler) throws ExpiredJwtException, MalformedJwtException, SignatureException {
public <T> T parse(String compact, JwtHandler<T> handler)
throws ExpiredJwtException, MalformedJwtException, SignatureException {
Assert.notNull(handler, "JwtHandler argument cannot be null.");
Assert.hasText(compact, "JWT String argument cannot be null or empty.");

Jwt jwt = parse(compact);

if (jwt instanceof Jws) {
Jws jws = (Jws)jwt;
Jws jws = (Jws) jwt;
Object body = jws.getBody();
if (body instanceof Claims) {
return handler.onClaimsJws((Jws<Claims>)jws);
return handler.onClaimsJws((Jws<Claims>) jws);
} else {
return handler.onPlaintextJws((Jws<String>)jws);
return handler.onPlaintextJws((Jws<String>) jws);
}
} else {
Object body = jwt.getBody();
Expand Down
16 changes: 16 additions & 0 deletions src/test/groovy/io/jsonwebtoken/JwtParserTest.groovy
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,22 @@ class JwtParserTest {
}
}

/*
@Test
void testParseWithPrematureJwt() {
Date nbf = new Date(System.currentTimeMillis() + 100000);
String compact = Jwts.builder().setSubject('Joe').setNotBefore(nbf).compact();
try {
Jwts.parser().parse(compact);
} catch (PrematureJwtException e) {
assertTrue e.getMessage().startsWith('JWT must not be accepted before ')
}
}
*/

// ========================================================================
// parsePlaintextJwt tests
// ========================================================================
Expand Down
32 changes: 22 additions & 10 deletions src/test/groovy/io/jsonwebtoken/JwtsTest.groovy
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ class JwtsTest {
@Test
void testParsePlaintextToken() {

def claims = [iss: 'joe', exp: 1300819380, 'http://example.com/is_root':true]
def claims = [iss: 'joe', exp: later(), 'http://example.com/is_root':true]

String jwt = Jwts.builder().setClaims(claims).compact();

Expand Down Expand Up @@ -220,10 +220,22 @@ class JwtsTest {
assertNull claims.getAudience()
}

private static Date dateWithOnlySecondPrecision() {
private static Date now() {
return dateWithOnlySecondPrecision(System.currentTimeMillis());
}

private static int later() {
return laterDate().getTime() / 1000;
}

private static Date laterDate(int seconds) {
return dateWithOnlySecondPrecision(System.currentTimeMillis() + (seconds * 1000));
}

private static Date laterDate() {
return laterDate(10000);
}

private static Date dateWithOnlySecondPrecision(long millis) {
long seconds = millis / 1000;
long secondOnlyPrecisionMillis = seconds * 1000;
Expand All @@ -232,14 +244,14 @@ class JwtsTest {

@Test
void testConvenienceExpiration() {
Date now = dateWithOnlySecondPrecision() //jwt exp only supports *seconds* since epoch:
String compact = Jwts.builder().setExpiration(now).compact();
Date then = laterDate();
String compact = Jwts.builder().setExpiration(then).compact();
Claims claims = Jwts.parser().parse(compact).body as Claims
def claimedDate = claims.getExpiration()
assertEquals claimedDate, now
assertEquals claimedDate, then

compact = Jwts.builder().setIssuer("Me")
.setExpiration(now) //set it
.setExpiration(then) //set it
.setExpiration(null) //null should remove it
.compact();

Expand All @@ -249,7 +261,7 @@ class JwtsTest {

@Test
void testConvenienceNotBefore() {
Date now = dateWithOnlySecondPrecision() //jwt exp only supports *seconds* since epoch:
Date now = now() //jwt exp only supports *seconds* since epoch:
String compact = Jwts.builder().setNotBefore(now).compact();
Claims claims = Jwts.parser().parse(compact).body as Claims
def claimedDate = claims.getNotBefore()
Expand All @@ -266,7 +278,7 @@ class JwtsTest {

@Test
void testConvenienceIssuedAt() {
Date now = dateWithOnlySecondPrecision() //jwt exp only supports *seconds* since epoch:
Date now = now() //jwt exp only supports *seconds* since epoch:
String compact = Jwts.builder().setIssuedAt(now).compact();
Claims claims = Jwts.parser().parse(compact).body as Claims
def claimedDate = claims.getIssuedAt()
Expand Down Expand Up @@ -370,7 +382,7 @@ class JwtsTest {
PublicKey publicKey = kp.getPublic();
PrivateKey privateKey = kp.getPrivate();

def claims = [iss: 'joe', exp: 1300819380, 'http://example.com/is_root':true]
def claims = [iss: 'joe', exp: later(), 'http://example.com/is_root':true]

String jwt = Jwts.builder().setClaims(claims).signWith(alg, privateKey).compact();

Expand All @@ -393,7 +405,7 @@ class JwtsTest {
byte[] key = new byte[64];
random.nextBytes(key);

def claims = [iss: 'joe', exp: 1300819380, 'http://example.com/is_root':true]
def claims = [iss: 'joe', exp: later(), 'http://example.com/is_root':true]

String jwt = Jwts.builder().setClaims(claims).signWith(alg, key).compact();

Expand Down

0 comments on commit 35a4282

Please sign in to comment.