I am using jjwt
for jwt token creation. Everything works fine when setting expiration date with local system time, i.e.
Date expDate = new Date(new Date().getTime() + 180000); //java.util.Date
But I tried using UTC format date time and signed the jwt token with same 3 min expiry date. And now it is throwing ExpiredJwtException
though even i am validating as soon as creating the token. I am using SimpleDateFormat for setting timezone to utc.
This is my code for creating token using jjwt in java:
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
sdf.setTimeZone(TimeZone.getTimeZone("UTC"));
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
Date expDate, issDate;
try {
expDate = (Date) simpleDateFormat.parse(sdf.format(new Date().getTime() + 180000));
issDate = (Date) simpleDateFormat.parse(sdf.format(new Date().getTime()));
JwtBuilder builder = Jwts.builder()
.setExpiration(expDate)
.setIssuedAt(issDate)
.setId(id)
.signWith(signingKey, signatureAlgorithm);
jwtToken = builder.compact();
} catch (ParseException ex) {
}
The token gets successfully created. I can verify the contents online as well. expDate is 3 min ahead of issDate. I am also calling method for verifying the token as soon as after it was created by passing that created token. My verification method has:
try {
Jwts.parser().setSigningKey(signingKey).parseClaimsJws(token);
log.info("jwt verification success");
} catch (ExpiredJwtException exJwt) {
log.info("expired jwt : \n{}", exJwt.getMessage());
} catch (JwtException e) {
log.info("tampered jwt");
}
But I am getting ExpiredJwtException
. The error is
expired jwt : JWT expired at 2019-05-17T01:24:48Z. Current time: 2019-05-17T07:06:48Z, a difference of 20520836 milliseconds. Allowed clock skew: 0 milliseconds.
From my log, the issued date and expiration date in my token at this time is:
issued date is: 2019-05-17T07:06:48.000+0545
expiry date is: 2019-05-17T07:09:48.000+0545
How is this happening? And thank you for you help.
There's no need for SimpleDateFormat
here, as Date
represents the number of milliseconds since the Unix Epoch, that is, midnight on January 1st 1970 (UTC).
What may cause confusion, however, is the toString()
method, as it applies the JVM's default time zone when generating a string representing that value.
As you are concerned about UTC, let me just bring your attention to what the Coordinated Universal Time (UTC) actually is: It is a time standard (and not a timezone) and it's determined by highly precise atomic clocks combined with the Earth's rotation.
The UTC time standard was adjusted several times until 1972, when leap seconds were introduced to keep UTC in line with the Earth's rotation, which is not entirely even, and less exact than atomic clocks. As the Earth’s rotation is slowing down, every now and then we have to insert leap seconds here and there:
While the internal value of Date
is intended to reflect UTC, it may not do so exactly due to those leap seconds.
Even though Date
suits your needs when it comes to UTC, you should avoid it. It's a legacy class now.
Java 8 introduced a new API for dates, times, instants and durations based on the ISO calendar system. The closest equivalent to Date
is Instant
which represents a timestamp, a moment on the timeline in UTC.
To capture the current moment in UTC, you can use the following:
Instant.now(); // Capture the current moment in UTC
And you can use the following to get a string representing such value:
Instant.now().toString(); // 2019-05-17T12:50:40.474Z
This string is formatted according the ISO 8601, where Z
indicates that the given time is in UTC.
For interoperability with JJWT, which doesn't support the java.time
types yet, you can create an instance of Date
from Instant
:
Date.from(Instant.now()); // Convert from modern class to legacy class
And here's a test that demonstrates how you can issue and validate a token:
@Test
public void shouldMatchIssuedAtAndExpiration() {
Key key = Keys.secretKeyFor(SignatureAlgorithm.HS256);
Instant issuedAt = Instant.now().truncatedTo(ChronoUnit.SECONDS);
Instant expiration = issuedAt.plus(3, ChronoUnit.MINUTES);
log.info("Issued at: {}", issuedAt);
log.info("Expires at: {}", expiration);
String jws = Jwts.builder()
.setIssuedAt(Date.from(issuedAt))
.setExpiration(Date.from(expiration))
.signWith(key)
.compact();
Claims claims = Jwts.parser()
.setSigningKey(key)
.parseClaimsJws(jws)
.getBody();
assertThat(claims.getIssuedAt().toInstant(), is(issuedAt));
assertThat(claims.getExpiration().toInstant(), is(expiration));
}
For the above example, I've used JJWT 0.10.5 with the dependencies listed in the documentation. In case you need, the above code was written with the following import
statements:
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.security.Keys;
import lombok.extern.slf4j.Slf4j;
import org.junit.Test;
import java.security.Key;
import java.time.Instant;
import java.time.ZoneOffset;
import java.time.temporal.ChronoUnit;
import java.util.Date;
import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.assertThat;