Issue #6: Fixed tests to not reference static (stale) expiration dates

This commit is contained in:
Les Hazlewood 2014-10-28 17:40:37 -07:00
parent 076fa3074c
commit 35a42826ea
4 changed files with 81 additions and 25 deletions

View File

@ -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
*/

View File

@ -20,8 +20,8 @@ import io.jsonwebtoken.Claims;
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;
@ -45,6 +45,9 @@ import java.util.Map;
@SuppressWarnings("unchecked")
public class DefaultJwtParser implements JwtParser {
//don't need millis since JWT date fields are only second granularity:
private static final String ISO_8601_FORMAT = "yyyy-MM-dd'T'HH:mm:ssZ";
private ObjectMapper objectMapper = new ObjectMapper();
private byte[] keyBytes;
@ -167,16 +170,18 @@ public class DefaultJwtParser implements JwtParser {
//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);
@ -184,12 +189,33 @@ public class DefaultJwtParser implements JwtParser {
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;
@ -218,7 +244,8 @@ public class DefaultJwtParser implements JwtParser {
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());
}
@ -241,26 +268,27 @@ public class DefaultJwtParser implements JwtParser {
Object body = claims != null ? claims : payload;
if (base64UrlEncodedDigest != null) {
return new DefaultJws<Object>((JwsHeader)header, body, base64UrlEncodedDigest);
return new DefaultJws<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();

View File

@ -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
// ========================================================================

View File

@ -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();
@ -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;
@ -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();
@ -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()
@ -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()
@ -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();
@ -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();