Issue #6: ensured that a (new) ExpiredJwtException is thrown when parsing an expired Claims JWT or JWS.

This commit is contained in:
Les Hazlewood 2014-10-28 16:41:15 -07:00
parent c0ff23ae6c
commit adbcf46de3
5 changed files with 125 additions and 6 deletions

View File

@ -88,6 +88,10 @@ These feature sets will be implemented in a future release when possible. Commu
## Release Notes ## Release Notes
### 0.3
Parsing a expired Claims JWT or JWS (as determined by the `exp` claims field) will now throw an `ExpiredJwtException`.
### 0.2 ### 0.2
#### More convenient Claims building #### More convenient Claims building

View File

@ -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 indicating that a JWT was referenced after it expired and should be rejected.
*
* @since 0.3
*/
public class ExpiredJwtException extends JwtException {
public ExpiredJwtException(String message) {
super(message);
}
public ExpiredJwtException(String message, Throwable cause) {
super(message, cause);
}
}

View File

@ -106,6 +106,8 @@ public interface JwtParser {
* JWTs should not be trusted and should be discarded. * JWTs should not be trusted and should be discarded.
* @throws SignatureException if a JWS signature was discovered, but could not be verified. JWTs that fail * @throws SignatureException if a JWS signature was discovered, but could not be verified. JWTs that fail
* signature validation should not be trusted and should be discarded. * signature validation should not be trusted and should be discarded.
* @throws ExpiredJwtException if the specified JWT is a Claims JWT and the Claims has an expiration time
* before the time this method is invoked.
* @throws IllegalArgumentException if the specified string is {@code null} or empty or only whitespace. * @throws IllegalArgumentException if the specified string is {@code null} or empty or only whitespace.
* @see #parse(String, JwtHandler) * @see #parse(String, JwtHandler)
* @see #parsePlaintextJwt(String) * @see #parsePlaintextJwt(String)
@ -113,7 +115,7 @@ public interface JwtParser {
* @see #parsePlaintextJws(String) * @see #parsePlaintextJws(String)
* @see #parseClaimsJws(String) * @see #parseClaimsJws(String)
*/ */
Jwt parse(String jwt) throws MalformedJwtException, SignatureException, IllegalArgumentException; Jwt parse(String jwt) throws ExpiredJwtException, MalformedJwtException, SignatureException, IllegalArgumentException;
/** /**
* Parses the specified compact serialized JWT string based on the builder's current configuration state and * Parses the specified compact serialized JWT string based on the builder's current configuration state and
@ -150,6 +152,8 @@ public interface JwtParser {
* Invalid JWTs should not be trusted and should be discarded. * Invalid JWTs should not be trusted and should be discarded.
* @throws SignatureException if a JWS signature was discovered, but could not be verified. JWTs that fail * @throws SignatureException if a JWS signature was discovered, but could not be verified. JWTs that fail
* signature validation should not be trusted and should be discarded. * signature validation should not be trusted and should be discarded.
* @throws ExpiredJwtException if the specified JWT is a Claims JWT and the Claims has an expiration time
* before the time this method is invoked.
* @throws IllegalArgumentException if the specified string is {@code null} or empty or only whitespace, or if the * @throws IllegalArgumentException if the specified string is {@code null} or empty or only whitespace, or if the
* {@code handler} is {@code null}. * {@code handler} is {@code null}.
* @see #parsePlaintextJwt(String) * @see #parsePlaintextJwt(String)
@ -160,7 +164,7 @@ public interface JwtParser {
* @since 0.2 * @since 0.2
*/ */
<T> T parse(String jwt, JwtHandler<T> handler) <T> T parse(String jwt, JwtHandler<T> handler)
throws 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 * Parses the specified compact serialized JWT string based on the builder's current configuration state and
@ -210,6 +214,8 @@ public interface JwtParser {
* @throws MalformedJwtException if the {@code claimsJwt} string is not a valid JWT * @throws MalformedJwtException if the {@code claimsJwt} string is not a valid JWT
* @throws SignatureException if the {@code claimsJwt} string is actually a JWS and signature validation * @throws SignatureException if the {@code claimsJwt} string is actually a JWS and signature validation
* fails * fails
* @throws ExpiredJwtException if the specified JWT is a Claims JWT and the Claims has an expiration time
* before the time this method is invoked.
* @throws IllegalArgumentException if the {@code claimsJwt} string is {@code null} or empty or only whitespace * @throws IllegalArgumentException if the {@code claimsJwt} string is {@code null} or empty or only whitespace
* @see #parsePlaintextJwt(String) * @see #parsePlaintextJwt(String)
* @see #parsePlaintextJws(String) * @see #parsePlaintextJws(String)
@ -219,7 +225,7 @@ public interface JwtParser {
* @since 0.2 * @since 0.2
*/ */
Jwt<Header, Claims> parseClaimsJwt(String claimsJwt) Jwt<Header, Claims> parseClaimsJwt(String claimsJwt)
throws 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 * Parses the specified compact serialized JWS string based on the builder's current configuration state and
@ -265,6 +271,8 @@ public interface JwtParser {
* @throws UnsupportedJwtException if the {@code claimsJws} argument does not represent an Claims JWS * @throws UnsupportedJwtException if the {@code claimsJws} argument does not represent an Claims JWS
* @throws MalformedJwtException if the {@code claimsJws} string is not a valid JWS * @throws MalformedJwtException if the {@code claimsJws} string is not a valid JWS
* @throws SignatureException if the {@code claimsJws} JWS signature validation fails * @throws SignatureException if the {@code claimsJws} JWS signature validation fails
* @throws ExpiredJwtException if the specified JWT is a Claims JWT and the Claims has an expiration time
* before the time this method is invoked.
* @throws IllegalArgumentException if the {@code claimsJws} string is {@code null} or empty or only whitespace * @throws IllegalArgumentException if the {@code claimsJws} string is {@code null} or empty or only whitespace
* @see #parsePlaintextJwt(String) * @see #parsePlaintextJwt(String)
* @see #parseClaimsJwt(String) * @see #parseClaimsJwt(String)
@ -274,5 +282,5 @@ public interface JwtParser {
* @since 0.2 * @since 0.2
*/ */
Jws<Claims> parseClaimsJws(String claimsJws) Jws<Claims> parseClaimsJws(String claimsJws)
throws UnsupportedJwtException, MalformedJwtException, SignatureException, IllegalArgumentException; throws ExpiredJwtException, UnsupportedJwtException, MalformedJwtException, SignatureException, IllegalArgumentException;
} }

View File

@ -17,6 +17,7 @@ package io.jsonwebtoken.impl;
import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectMapper;
import io.jsonwebtoken.Claims; import io.jsonwebtoken.Claims;
import io.jsonwebtoken.ExpiredJwtException;
import io.jsonwebtoken.Header; import io.jsonwebtoken.Header;
import io.jsonwebtoken.Jws; import io.jsonwebtoken.Jws;
import io.jsonwebtoken.Jwt; import io.jsonwebtoken.Jwt;
@ -37,6 +38,8 @@ import io.jsonwebtoken.lang.Strings;
import javax.crypto.spec.SecretKeySpec; import javax.crypto.spec.SecretKeySpec;
import java.io.IOException; import java.io.IOException;
import java.security.Key; import java.security.Key;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Map; import java.util.Map;
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
@ -94,7 +97,7 @@ public class DefaultJwtParser implements JwtParser {
} }
@Override @Override
public Jwt parse(String jwt) throws MalformedJwtException, SignatureException { public Jwt parse(String jwt) throws ExpiredJwtException, MalformedJwtException, SignatureException {
Assert.hasText(jwt, "JWT String argument cannot be null or empty."); Assert.hasText(jwt, "JWT String argument cannot be null or empty.");
@ -161,6 +164,28 @@ public class DefaultJwtParser implements JwtParser {
claims = new DefaultClaims(claimsMap); claims = new DefaultClaims(claimsMap);
} }
//since 0.3:
if (claims != null) {
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
String expVal = sdf.format(exp);
String nowVal = sdf.format(now);
String msg = "JWT expired at " + expVal + ". Current time: " + nowVal;
throw new ExpiredJwtException(msg);
}
}
}
// =============== Signature ================= // =============== Signature =================
if (base64UrlEncodedDigest != null) { //it is signed - validate the signature if (base64UrlEncodedDigest != null) { //it is signed - validate the signature
@ -223,7 +248,7 @@ public class DefaultJwtParser implements JwtParser {
} }
@Override @Override
public <T> T parse(String compact, JwtHandler<T> handler) throws MalformedJwtException, SignatureException { public <T> T parse(String compact, JwtHandler<T> handler) throws ExpiredJwtException, MalformedJwtException, SignatureException {
Assert.notNull(handler, "JwtHandler argument cannot be null."); Assert.notNull(handler, "JwtHandler argument cannot be null.");
Assert.hasText(compact, "JWT String argument cannot be null or empty."); Assert.hasText(compact, "JWT String argument cannot be null or empty.");

View File

@ -160,6 +160,20 @@ class JwtParserTest {
assertEquals jwt.body, payload assertEquals jwt.body, payload
} }
@Test
void testParseWithExpiredJwt() {
Date exp = new Date(System.currentTimeMillis() - 1000);
String compact = Jwts.builder().setSubject('Joe').setExpiration(exp).compact();
try {
Jwts.parser().parse(compact);
} catch (ExpiredJwtException e) {
assertTrue e.getMessage().startsWith('JWT expired at ')
}
}
// ======================================================================== // ========================================================================
// parsePlaintextJwt tests // parsePlaintextJwt tests
// ======================================================================== // ========================================================================
@ -275,6 +289,23 @@ class JwtParserTest {
} }
} }
@Test
void testParseClaimsJwtWithExpiredJwt() {
long nowMillis = System.currentTimeMillis();
//some time in the past:
Date exp = new Date(nowMillis - 1000);
String compact = Jwts.builder().setSubject('Joe').setExpiration(exp).compact()
try {
Jwts.parser().parseClaimsJwt(compact);
fail();
} catch (ExpiredJwtException e) {
assertTrue e.getMessage().startsWith('JWT expired at ')
}
}
// ======================================================================== // ========================================================================
// parsePlaintextJws tests // parsePlaintextJws tests
// ======================================================================== // ========================================================================
@ -359,6 +390,25 @@ class JwtParserTest {
assertEquals jwt.getBody().getSubject(), sub assertEquals jwt.getBody().getSubject(), sub
} }
@Test
void testParseClaimsJwsWithExpiredJws() {
byte[] key = randomKey()
long nowMillis = System.currentTimeMillis();
//some time in the past:
Date exp = new Date(nowMillis - 1000);
String compact = Jwts.builder().setSubject('Joe').signWith(SignatureAlgorithm.HS256, key).setExpiration(exp).compact()
try {
Jwts.parser().parseClaimsJwt(compact);
fail();
} catch (ExpiredJwtException e) {
assertTrue e.getMessage().startsWith('JWT expired at ')
}
}
@Test @Test
void testParseClaimsJwsWithPlaintextJwt() { void testParseClaimsJwsWithPlaintextJwt() {