mirror of https://github.com/jwtk/jjwt.git
Issue #6: ensured that a (new) ExpiredJwtException is thrown when parsing an expired Claims JWT or JWS.
This commit is contained in:
parent
c0ff23ae6c
commit
adbcf46de3
|
@ -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
|
||||||
|
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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.");
|
||||||
|
|
||||||
|
|
|
@ -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() {
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue