mirror of https://github.com/jwtk/jjwt.git
commit
9e1ee67582
|
@ -0,0 +1,22 @@
|
||||||
|
package io.jsonwebtoken;
|
||||||
|
|
||||||
|
import io.jsonwebtoken.impl.DefaultClock;
|
||||||
|
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A clock represents a time source that can be used when creating and verifying JWTs.
|
||||||
|
*
|
||||||
|
* @since 0.7.0
|
||||||
|
*/
|
||||||
|
public interface Clock {
|
||||||
|
|
||||||
|
public static final Clock DEFAULT = new DefaultClock();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the clock's current timestamp at the instant the method is invoked.
|
||||||
|
*
|
||||||
|
* @return the clock's current timestamp at the instant the method is invoked.
|
||||||
|
*/
|
||||||
|
Date now();
|
||||||
|
}
|
|
@ -15,6 +15,8 @@
|
||||||
*/
|
*/
|
||||||
package io.jsonwebtoken;
|
package io.jsonwebtoken;
|
||||||
|
|
||||||
|
import io.jsonwebtoken.impl.DefaultClock;
|
||||||
|
|
||||||
import java.security.Key;
|
import java.security.Key;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
|
|
||||||
|
@ -124,6 +126,16 @@ public interface JwtParser {
|
||||||
*/
|
*/
|
||||||
JwtParser require(String claimName, Object value);
|
JwtParser require(String claimName, Object value);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the {@link Clock} that determines the timestamp to use when validating the parsed JWT.
|
||||||
|
* The parser uses a {@link DefaultClock DefaultClock} instance by default.
|
||||||
|
*
|
||||||
|
* @param clock a {@code Clock} object to return the timestamp to use when validating the parsed JWT.
|
||||||
|
* @return the builder instance for method chaining.
|
||||||
|
* @since 0.7.0
|
||||||
|
*/
|
||||||
|
JwtParser setClock(Clock clock);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the signing key used to verify any discovered JWS digital signature. If the specified JWT string is not
|
* Sets the signing key used to verify any discovered JWS digital signature. If the specified JWT string is not
|
||||||
* a JWS (no signature), this key is not used.
|
* a JWS (no signature), this key is not used.
|
||||||
|
|
|
@ -0,0 +1,23 @@
|
||||||
|
package io.jsonwebtoken.impl;
|
||||||
|
|
||||||
|
import io.jsonwebtoken.Clock;
|
||||||
|
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Default {@link Clock} implementation.
|
||||||
|
*
|
||||||
|
* @since 0.7.0
|
||||||
|
*/
|
||||||
|
public class DefaultClock implements Clock {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Simply returns <code>new {@link Date}()</code>.
|
||||||
|
*
|
||||||
|
* @return a new {@link Date} instance.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public Date now() {
|
||||||
|
return new Date();
|
||||||
|
}
|
||||||
|
}
|
|
@ -18,6 +18,7 @@ package io.jsonwebtoken.impl;
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
import io.jsonwebtoken.ClaimJwtException;
|
import io.jsonwebtoken.ClaimJwtException;
|
||||||
import io.jsonwebtoken.Claims;
|
import io.jsonwebtoken.Claims;
|
||||||
|
import io.jsonwebtoken.Clock;
|
||||||
import io.jsonwebtoken.CompressionCodec;
|
import io.jsonwebtoken.CompressionCodec;
|
||||||
import io.jsonwebtoken.CompressionCodecResolver;
|
import io.jsonwebtoken.CompressionCodecResolver;
|
||||||
import io.jsonwebtoken.ExpiredJwtException;
|
import io.jsonwebtoken.ExpiredJwtException;
|
||||||
|
@ -69,6 +70,8 @@ public class DefaultJwtParser implements JwtParser {
|
||||||
|
|
||||||
Claims expectedClaims = new DefaultClaims();
|
Claims expectedClaims = new DefaultClaims();
|
||||||
|
|
||||||
|
private Clock clock = Clock.DEFAULT;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public JwtParser requireIssuedAt(Date issuedAt) {
|
public JwtParser requireIssuedAt(Date issuedAt) {
|
||||||
expectedClaims.setIssuedAt(issuedAt);
|
expectedClaims.setIssuedAt(issuedAt);
|
||||||
|
@ -127,6 +130,13 @@ public class DefaultJwtParser implements JwtParser {
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public JwtParser setClock(Clock clock) {
|
||||||
|
Assert.notNull(clock, "Clock instance cannot be null.");
|
||||||
|
this.clock = clock;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public JwtParser setSigningKey(byte[] key) {
|
public JwtParser setSigningKey(byte[] key) {
|
||||||
Assert.notEmpty(key, "signing key cannot be null or empty.");
|
Assert.notEmpty(key, "signing key cannot be null or empty.");
|
||||||
|
@ -346,16 +356,15 @@ public class DefaultJwtParser implements JwtParser {
|
||||||
//since 0.3:
|
//since 0.3:
|
||||||
if (claims != null) {
|
if (claims != null) {
|
||||||
|
|
||||||
Date now = null;
|
|
||||||
SimpleDateFormat sdf;
|
SimpleDateFormat sdf;
|
||||||
|
|
||||||
|
final Date now = this.clock.now();
|
||||||
|
|
||||||
//https://tools.ietf.org/html/draft-ietf-oauth-json-web-token-30#section-4.1.4
|
//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:
|
//token MUST NOT be accepted on or after any specified exp time:
|
||||||
Date exp = claims.getExpiration();
|
Date exp = claims.getExpiration();
|
||||||
if (exp != null) {
|
if (exp != null) {
|
||||||
|
|
||||||
now = new Date();
|
|
||||||
|
|
||||||
if (now.equals(exp) || now.after(exp)) {
|
if (now.equals(exp) || now.after(exp)) {
|
||||||
sdf = new SimpleDateFormat(ISO_8601_FORMAT);
|
sdf = new SimpleDateFormat(ISO_8601_FORMAT);
|
||||||
String expVal = sdf.format(exp);
|
String expVal = sdf.format(exp);
|
||||||
|
@ -371,10 +380,6 @@ public class DefaultJwtParser implements JwtParser {
|
||||||
Date nbf = claims.getNotBefore();
|
Date nbf = claims.getNotBefore();
|
||||||
if (nbf != null) {
|
if (nbf != null) {
|
||||||
|
|
||||||
if (now == null) {
|
|
||||||
now = new Date();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (now.before(nbf)) {
|
if (now.before(nbf)) {
|
||||||
sdf = new SimpleDateFormat(ISO_8601_FORMAT);
|
sdf = new SimpleDateFormat(ISO_8601_FORMAT);
|
||||||
String nbfVal = sdf.format(nbf);
|
String nbfVal = sdf.format(nbf);
|
||||||
|
|
|
@ -0,0 +1,39 @@
|
||||||
|
package io.jsonwebtoken.impl;
|
||||||
|
|
||||||
|
import io.jsonwebtoken.Clock;
|
||||||
|
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A {@code Clock} implementation that is constructed with a seed timestamp and always reports that same
|
||||||
|
* timestamp.
|
||||||
|
*
|
||||||
|
* @since 0.7.0
|
||||||
|
*/
|
||||||
|
public class FixedClock implements Clock {
|
||||||
|
|
||||||
|
private final Date now;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new fixed clock using <code>new {@link Date Date}()</code> as the seed timestamp. All calls to
|
||||||
|
* {@link #now now()} will always return this seed Date.
|
||||||
|
*/
|
||||||
|
public FixedClock() {
|
||||||
|
this(new Date());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new fixed clock using the specified seed timestamp. All calls to
|
||||||
|
* {@link #now now()} will always return this seed Date.
|
||||||
|
*
|
||||||
|
* @param now the specified Date to always return from all calls to {@link #now now()}.
|
||||||
|
*/
|
||||||
|
public FixedClock(Date now) {
|
||||||
|
this.now = now;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Date now() {
|
||||||
|
return this.now;
|
||||||
|
}
|
||||||
|
}
|
|
@ -15,6 +15,8 @@
|
||||||
*/
|
*/
|
||||||
package io.jsonwebtoken
|
package io.jsonwebtoken
|
||||||
|
|
||||||
|
import io.jsonwebtoken.impl.DefaultClock
|
||||||
|
import io.jsonwebtoken.impl.FixedClock
|
||||||
import io.jsonwebtoken.impl.TextCodec
|
import io.jsonwebtoken.impl.TextCodec
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
|
|
||||||
|
@ -1392,4 +1394,39 @@ class JwtParserTest {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testParseClockManipulationWithFixedClock() {
|
||||||
|
def then = System.currentTimeMillis() - 1000
|
||||||
|
Date expiry = new Date(then)
|
||||||
|
Date beforeExpiry = new Date(then - 1000)
|
||||||
|
|
||||||
|
String compact = Jwts.builder().setSubject('Joe').setExpiration(expiry).compact()
|
||||||
|
|
||||||
|
Jwts.parser().setClock(new FixedClock(beforeExpiry)).parse(compact)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testParseClockManipulationWithNullClock() {
|
||||||
|
JwtParser parser = Jwts.parser();
|
||||||
|
try {
|
||||||
|
parser.setClock(null)
|
||||||
|
fail()
|
||||||
|
} catch (IllegalArgumentException expected) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testParseClockManipulationWithDefaultClock() {
|
||||||
|
Date expiry = new Date(System.currentTimeMillis() - 1000)
|
||||||
|
|
||||||
|
String compact = Jwts.builder().setSubject('Joe').setExpiration(expiry).compact()
|
||||||
|
|
||||||
|
try {
|
||||||
|
Jwts.parser().setClock(new DefaultClock()).parse(compact)
|
||||||
|
fail()
|
||||||
|
} catch (ExpiredJwtException e) {
|
||||||
|
assertTrue e.getMessage().startsWith('JWT expired at ')
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,19 @@
|
||||||
|
package io.jsonwebtoken.impl
|
||||||
|
|
||||||
|
import org.junit.Test
|
||||||
|
import static org.junit.Assert.*
|
||||||
|
|
||||||
|
class FixedClockTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testFixedClockDefaultConstructor() {
|
||||||
|
|
||||||
|
def clock = new FixedClock()
|
||||||
|
|
||||||
|
def date1 = clock.now()
|
||||||
|
Thread.sleep(100)
|
||||||
|
def date2 = clock.now()
|
||||||
|
|
||||||
|
assertSame date1, date2
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue