mirror of https://github.com/jwtk/jjwt.git
Closes #816 Fixed exception message per recommendation. Also updated expiration message to be clearer/intuitive.
This commit is contained in:
parent
e9df2da272
commit
a2b65763e9
|
@ -658,42 +658,41 @@ public class DefaultJwtParser implements JwtParser {
|
|||
final Date now = this.clock.now();
|
||||
long nowTime = now.getTime();
|
||||
|
||||
//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:
|
||||
// https://www.rfc-editor.org/rfc/rfc7519.html#section-4.1.4
|
||||
// token MUST NOT be accepted on or after any specified exp time:
|
||||
Date exp = claims.getExpiration();
|
||||
if (exp != null) {
|
||||
|
||||
long maxTime = nowTime - this.allowedClockSkewMillis;
|
||||
Date max = allowSkew ? new Date(maxTime) : now;
|
||||
if (max.after(exp)) {
|
||||
String expVal = DateFormats.formatIso8601(exp, false);
|
||||
String nowVal = DateFormats.formatIso8601(now, false);
|
||||
String expVal = DateFormats.formatIso8601(exp, true);
|
||||
String nowVal = DateFormats.formatIso8601(now, true);
|
||||
|
||||
long differenceMillis = nowTime - exp.getTime();
|
||||
|
||||
String msg = "JWT expired at " + expVal + ". Current time: " + nowVal + ", a difference of " +
|
||||
differenceMillis + " milliseconds. Allowed clock skew: " +
|
||||
String msg = "JWT expired " + differenceMillis + " milliseconds ago at " + expVal + ". " +
|
||||
"Current time: " + nowVal + ". Allowed clock skew: " +
|
||||
this.allowedClockSkewMillis + " milliseconds.";
|
||||
throw new ExpiredJwtException(header, claims, 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:
|
||||
// https://www.rfc-editor.org/rfc/rfc7519.html#section-4.1.5
|
||||
// token MUST NOT be accepted before any specified nbf time:
|
||||
Date nbf = claims.getNotBefore();
|
||||
if (nbf != null) {
|
||||
|
||||
long minTime = nowTime + this.allowedClockSkewMillis;
|
||||
Date min = allowSkew ? new Date(minTime) : now;
|
||||
if (min.before(nbf)) {
|
||||
String nbfVal = DateFormats.formatIso8601(nbf, false);
|
||||
String nowVal = DateFormats.formatIso8601(now, false);
|
||||
String nbfVal = DateFormats.formatIso8601(nbf, true);
|
||||
String nowVal = DateFormats.formatIso8601(now, true);
|
||||
|
||||
long differenceMillis = nbf.getTime() - minTime;
|
||||
long differenceMillis = nbf.getTime() - nowTime;
|
||||
|
||||
String msg = "JWT must not be accepted before " + nbfVal + ". Current time: " + nowVal +
|
||||
", a difference of " +
|
||||
differenceMillis + " milliseconds. Allowed clock skew: " +
|
||||
String msg = "JWT early by " + differenceMillis + " milliseconds before " + nbfVal +
|
||||
". Current time: " + nowVal + ". Allowed clock skew: " +
|
||||
this.allowedClockSkewMillis + " milliseconds.";
|
||||
throw new PrematureJwtException(header, claims, msg);
|
||||
}
|
||||
|
|
|
@ -15,11 +15,13 @@
|
|||
*/
|
||||
package io.jsonwebtoken
|
||||
|
||||
import io.jsonwebtoken.impl.DefaultClock
|
||||
|
||||
import io.jsonwebtoken.impl.DefaultJwtParser
|
||||
import io.jsonwebtoken.impl.FixedClock
|
||||
import io.jsonwebtoken.impl.JwtTokenizer
|
||||
import io.jsonwebtoken.impl.lang.JwtDateConverter
|
||||
import io.jsonwebtoken.io.Encoders
|
||||
import io.jsonwebtoken.lang.DateFormats
|
||||
import io.jsonwebtoken.lang.Strings
|
||||
import io.jsonwebtoken.security.Keys
|
||||
import io.jsonwebtoken.security.SignatureException
|
||||
|
@ -224,52 +226,80 @@ class JwtParserTest {
|
|||
} catch (ExpiredJwtException e) {
|
||||
// https://github.com/jwtk/jjwt/issues/107 (the Z designator at the end of the timestamp):
|
||||
// https://github.com/jwtk/jjwt/issues/660 (show differences as now - expired)
|
||||
assertEquals e.getMessage(), "JWT expired at 2022-07-11T15:15:36Z. Current time: " +
|
||||
"2022-07-11T15:15:37Z, a difference of 1573 milliseconds. Allowed clock skew: 0 milliseconds."
|
||||
String msg = "JWT expired 1573 milliseconds ago at 2022-07-11T15:15:36.000Z. " +
|
||||
"Current time: 2022-07-11T15:15:37.573Z. Allowed clock skew: 0 milliseconds."
|
||||
assertEquals msg, e.message
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void testParseWithPrematureJwt() {
|
||||
|
||||
Date nbf = new Date(System.currentTimeMillis() + 100000)
|
||||
long differenceMillis = 100000 // arbitrary, anything > 0 is fine
|
||||
def nbf = JwtDateConverter.INSTANCE.applyFrom(System.currentTimeMillis() / 1000L)
|
||||
def earlier = new Date(nbf.getTime() - differenceMillis)
|
||||
|
||||
String compact = Jwts.builder().setSubject('Joe').setNotBefore(nbf).compact()
|
||||
String compact = Jwts.builder().subject('Joe').notBefore(nbf).compact()
|
||||
|
||||
try {
|
||||
Jwts.parser().enableUnsecured().build().parse(compact)
|
||||
Jwts.parser().enableUnsecured().clock(new FixedClock(earlier)).build().parse(compact)
|
||||
fail()
|
||||
} catch (PrematureJwtException e) {
|
||||
assertTrue e.getMessage().startsWith('JWT must not be accepted before ')
|
||||
def nbf8601 = DateFormats.formatIso8601(nbf, true)
|
||||
def earlier8601 = DateFormats.formatIso8601(earlier, true)
|
||||
String msg = "JWT early by ${differenceMillis} milliseconds before ${nbf8601}. " +
|
||||
"Current time: ${earlier8601}. Allowed clock skew: 0 milliseconds.";
|
||||
assertEquals msg, e.message
|
||||
|
||||
//https://github.com/jwtk/jjwt/issues/107 (the Z designator at the end of the timestamp):
|
||||
assertTrue e.getMessage().contains('Z, a difference of ')
|
||||
assertTrue nbf8601.endsWith('Z')
|
||||
assertTrue earlier8601.endsWith('Z')
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void testParseWithExpiredJwtWithinAllowedClockSkew() {
|
||||
Date exp = new Date(System.currentTimeMillis() - 3000)
|
||||
|
||||
long differenceMillis = 3000 // arbitrary, anything > 0 is fine
|
||||
long millis = System.currentTimeMillis()
|
||||
// RFC requires time in seconds, so we need to base our assertions based on second-normalized dates,
|
||||
// otherwise we'll get nondeterministic tests:
|
||||
long seconds = (millis / 1000L).longValue()
|
||||
millis = seconds * 1000L
|
||||
def exp = new Date(millis)
|
||||
def later = new Date(exp.getTime() + differenceMillis)
|
||||
def s = Jwts.builder().expiration(exp).compact()
|
||||
|
||||
String subject = 'Joe'
|
||||
String compact = Jwts.builder().setSubject(subject).setExpiration(exp).compact()
|
||||
String compact = Jwts.builder().subject(subject).expiration(exp).compact()
|
||||
|
||||
Jwt<Header, Claims> jwt = Jwts.parser().enableUnsecured().setAllowedClockSkewSeconds(10).build().parse(compact)
|
||||
Jwt<Header, Claims> jwt = Jwts.parser().enableUnsecured().setAllowedClockSkewSeconds(10)
|
||||
.clock(new FixedClock(later)).build().parse(compact)
|
||||
|
||||
assertEquals jwt.getPayload().getSubject(), subject
|
||||
}
|
||||
|
||||
@Test
|
||||
void testParseWithExpiredJwtNotWithinAllowedClockSkew() {
|
||||
Date exp = new Date(System.currentTimeMillis() - 3000)
|
||||
|
||||
String compact = Jwts.builder().setSubject('Joe').setExpiration(exp).compact()
|
||||
long differenceMillis = 3000 // arbitrary, anything > 0 is fine
|
||||
def exp = JwtDateConverter.INSTANCE.applyFrom(System.currentTimeMillis() / 1000L)
|
||||
def later = new Date(exp.getTime() + differenceMillis)
|
||||
|
||||
def s = Jwts.builder().expiration(exp).compact()
|
||||
|
||||
def skewSeconds = 1
|
||||
|
||||
try {
|
||||
Jwts.parser().enableUnsecured().setAllowedClockSkewSeconds(1).build().parse(compact)
|
||||
Jwts.parser().enableUnsecured().setAllowedClockSkewSeconds(skewSeconds)
|
||||
.clock(new FixedClock(later)).build().parse(s)
|
||||
fail()
|
||||
} catch (ExpiredJwtException e) {
|
||||
assertTrue e.getMessage().startsWith('JWT expired at ')
|
||||
def exp8601 = DateFormats.formatIso8601(exp, true)
|
||||
def later8601 = DateFormats.formatIso8601(later, true)
|
||||
String msg = "JWT expired ${differenceMillis} milliseconds ago at ${exp8601}. " +
|
||||
"Current time: ${later8601}. Allowed clock skew: ${skewSeconds * 1000} milliseconds.";
|
||||
assertEquals msg, e.message
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -287,15 +317,26 @@ class JwtParserTest {
|
|||
|
||||
@Test
|
||||
void testParseWithPrematureJwtNotWithinAllowedClockSkew() {
|
||||
Date exp = new Date(System.currentTimeMillis() + 3000)
|
||||
|
||||
String compact = Jwts.builder().setSubject('Joe').setNotBefore(exp).compact()
|
||||
long differenceMillis = 3000 // arbitrary, anything > 0 is fine
|
||||
def nbf = JwtDateConverter.INSTANCE.applyFrom(System.currentTimeMillis() / 1000L)
|
||||
def earlier = new Date(nbf.getTime() - differenceMillis)
|
||||
|
||||
String compact = Jwts.builder().subject('Joe').notBefore(nbf).compact()
|
||||
|
||||
def skewSeconds = 1
|
||||
|
||||
try {
|
||||
Jwts.parser().enableUnsecured().setAllowedClockSkewSeconds(1).build().parse(compact)
|
||||
Jwts.parser().enableUnsecured()
|
||||
.setAllowedClockSkewSeconds(skewSeconds).clock(new FixedClock(earlier))
|
||||
.build().parse(compact)
|
||||
fail()
|
||||
} catch (PrematureJwtException e) {
|
||||
assertTrue e.getMessage().startsWith('JWT must not be accepted before ')
|
||||
def nbf8601 = DateFormats.formatIso8601(nbf, true)
|
||||
def earlier8601 = DateFormats.formatIso8601(earlier, true)
|
||||
String msg = "JWT early by ${differenceMillis} milliseconds before ${nbf8601}. " +
|
||||
"Current time: ${earlier8601}. Allowed clock skew: ${skewSeconds * 1000} milliseconds.";
|
||||
assertEquals msg, e.message
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -417,38 +458,6 @@ 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().enableUnsecured().build().parseClaimsJwt(compact)
|
||||
fail()
|
||||
} catch (ExpiredJwtException e) {
|
||||
assertTrue e.getMessage().startsWith('JWT expired at ')
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void testParseClaimsJwtWithPrematureJwt() {
|
||||
|
||||
Date nbf = new Date(System.currentTimeMillis() + 100000)
|
||||
|
||||
String compact = Jwts.builder().setSubject('Joe').setNotBefore(nbf).compact()
|
||||
|
||||
try {
|
||||
Jwts.parser().enableUnsecured().build().parseClaimsJwt(compact)
|
||||
fail()
|
||||
} catch (PrematureJwtException e) {
|
||||
assertTrue e.getMessage().startsWith('JWT must not be accepted before ')
|
||||
}
|
||||
}
|
||||
|
||||
// ========================================================================
|
||||
// parseContentJws tests
|
||||
// ========================================================================
|
||||
|
@ -542,21 +551,23 @@ class JwtParserTest {
|
|||
@Test
|
||||
void testParseClaimsJwsWithExpiredJws() {
|
||||
|
||||
long differenceMillis = 843 // arbitrary, anything > 0 is fine
|
||||
def exp = JwtDateConverter.INSTANCE.applyFrom(System.currentTimeMillis() / 1000L)
|
||||
def later = new Date(exp.getTime() + differenceMillis)
|
||||
|
||||
String sub = 'Joe'
|
||||
|
||||
byte[] key = randomKey()
|
||||
|
||||
long nowMillis = System.currentTimeMillis()
|
||||
//some time in the past:
|
||||
Date exp = new Date(nowMillis - 1000)
|
||||
|
||||
String compact = Jwts.builder().setSubject(sub).signWith(SignatureAlgorithm.HS256, key).setExpiration(exp).compact()
|
||||
String compact = Jwts.builder().subject(sub).expiration(exp).signWith(SignatureAlgorithm.HS256, key).compact()
|
||||
|
||||
try {
|
||||
Jwts.parser().setSigningKey(key).build().parseClaimsJwt(compact)
|
||||
Jwts.parser().setSigningKey(key).clock(new FixedClock(later)).build().parseClaimsJwt(compact)
|
||||
fail()
|
||||
} catch (ExpiredJwtException e) {
|
||||
assertTrue e.getMessage().startsWith('JWT expired at ')
|
||||
def exp8601 = DateFormats.formatIso8601(exp, true)
|
||||
def later8601 = DateFormats.formatIso8601(later, true)
|
||||
String msg = "JWT expired ${differenceMillis} milliseconds ago at ${exp8601}. " +
|
||||
"Current time: ${later8601}. Allowed clock skew: 0 milliseconds.";
|
||||
assertEquals msg, e.message
|
||||
assertEquals e.getClaims().getSubject(), sub
|
||||
assertEquals e.getHeader().getAlgorithm(), "HS256"
|
||||
}
|
||||
|
@ -565,19 +576,24 @@ class JwtParserTest {
|
|||
@Test
|
||||
void testParseClaimsJwsWithPrematureJws() {
|
||||
|
||||
long differenceMillis = 3842 // arbitrary, anything > 0 is fine
|
||||
def nbf = JwtDateConverter.INSTANCE.applyFrom(System.currentTimeMillis() / 1000L)
|
||||
def earlier = new Date(nbf.getTime() - differenceMillis)
|
||||
|
||||
String sub = 'Joe'
|
||||
|
||||
byte[] key = randomKey()
|
||||
|
||||
Date nbf = new Date(System.currentTimeMillis() + 100000)
|
||||
|
||||
String compact = Jwts.builder().setSubject(sub).setNotBefore(nbf).signWith(SignatureAlgorithm.HS256, key).compact()
|
||||
String compact = Jwts.builder().subject(sub).notBefore(nbf).signWith(SignatureAlgorithm.HS256, key).compact()
|
||||
|
||||
try {
|
||||
Jwts.parser().setSigningKey(key).build().parseClaimsJws(compact)
|
||||
Jwts.parser().setSigningKey(key).clock(new FixedClock(earlier)).build().parseClaimsJws(compact)
|
||||
fail()
|
||||
} catch (PrematureJwtException e) {
|
||||
assertTrue e.getMessage().startsWith('JWT must not be accepted before ')
|
||||
def nbf8601 = DateFormats.formatIso8601(nbf, true)
|
||||
def earlier8601 = DateFormats.formatIso8601(earlier, true)
|
||||
String msg = "JWT early by ${differenceMillis} milliseconds before ${nbf8601}. " +
|
||||
"Current time: ${earlier8601}. Allowed clock skew: 0 milliseconds.";
|
||||
assertEquals msg, e.message
|
||||
|
||||
assertEquals e.getClaims().getSubject(), sub
|
||||
assertEquals e.getHeader().getAlgorithm(), "HS256"
|
||||
}
|
||||
|
@ -1545,20 +1561,6 @@ class JwtParserTest {
|
|||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void testParseClockManipulationWithDefaultClock() {
|
||||
Date expiry = new Date(System.currentTimeMillis() - 1000)
|
||||
|
||||
String compact = Jwts.builder().setSubject('Joe').setExpiration(expiry).compact()
|
||||
|
||||
try {
|
||||
Jwts.parser().enableUnsecured().setClock(new DefaultClock()).build().parse(compact)
|
||||
fail()
|
||||
} catch (ExpiredJwtException e) {
|
||||
assertTrue e.getMessage().startsWith('JWT expired at ')
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void testParseMalformedJwt() {
|
||||
|
||||
|
|
|
@ -16,11 +16,9 @@
|
|||
package io.jsonwebtoken.impl
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper
|
||||
import io.jsonwebtoken.Jwts
|
||||
import io.jsonwebtoken.MalformedJwtException
|
||||
import io.jsonwebtoken.SignatureAlgorithm
|
||||
import io.jsonwebtoken.UnsupportedJwtException
|
||||
import io.jsonwebtoken.*
|
||||
import io.jsonwebtoken.impl.lang.Bytes
|
||||
import io.jsonwebtoken.impl.lang.JwtDateConverter
|
||||
import io.jsonwebtoken.impl.lang.Services
|
||||
import io.jsonwebtoken.impl.security.TestKeys
|
||||
import io.jsonwebtoken.io.DeserializationException
|
||||
|
@ -28,6 +26,7 @@ import io.jsonwebtoken.io.Deserializer
|
|||
import io.jsonwebtoken.io.Encoders
|
||||
import io.jsonwebtoken.io.Serializer
|
||||
import io.jsonwebtoken.lang.Collections
|
||||
import io.jsonwebtoken.lang.DateFormats
|
||||
import io.jsonwebtoken.lang.Strings
|
||||
import io.jsonwebtoken.security.Keys
|
||||
import org.junit.Before
|
||||
|
@ -251,4 +250,42 @@ class DefaultJwtParserTest {
|
|||
assertEquals 'whatever', parsedCrit.iterator().next()
|
||||
assertEquals 42, jwt.getHeader().get('whatever')
|
||||
}
|
||||
|
||||
@Test
|
||||
void testExpiredExceptionMessage() {
|
||||
|
||||
long differenceMillis = 843 // arbitrary, anything > 0 is fine
|
||||
def exp = JwtDateConverter.INSTANCE.applyFrom(System.currentTimeMillis() / 1000L)
|
||||
def later = new Date(exp.getTime() + differenceMillis)
|
||||
def s = Jwts.builder().expiration(exp).compact()
|
||||
|
||||
try {
|
||||
Jwts.parser().enableUnsecured().clock(new FixedClock(later)).build().parseClaimsJwt(s)
|
||||
} catch (ExpiredJwtException expected) {
|
||||
def exp8601 = DateFormats.formatIso8601(exp, true)
|
||||
def later8601 = DateFormats.formatIso8601(later, true)
|
||||
String msg = "JWT expired ${differenceMillis} milliseconds ago at ${exp8601}. " +
|
||||
"Current time: ${later8601}. Allowed clock skew: 0 milliseconds.";
|
||||
assertEquals msg, expected.message
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void testNotBeforeExceptionMessage() {
|
||||
|
||||
long differenceMillis = 3842 // arbitrary, anything > 0 is fine
|
||||
def nbf = JwtDateConverter.INSTANCE.applyFrom(System.currentTimeMillis() / 1000L)
|
||||
def earlier = new Date(nbf.getTime() - differenceMillis)
|
||||
def s = Jwts.builder().notBefore(nbf).compact()
|
||||
|
||||
try {
|
||||
Jwts.parser().enableUnsecured().clock(new FixedClock(earlier)).build().parseClaimsJwt(s)
|
||||
} catch (PrematureJwtException expected) {
|
||||
def nbf8601 = DateFormats.formatIso8601(nbf, true)
|
||||
def earlier8601 = DateFormats.formatIso8601(earlier, true)
|
||||
String msg = "JWT early by ${differenceMillis} milliseconds before ${nbf8601}. " +
|
||||
"Current time: ${earlier8601}. Allowed clock skew: 0 milliseconds.";
|
||||
assertEquals msg, expected.message
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue