在UTC中使用到期日期时,JWT的到期不起作用

时间:2019-05-17 07:12:25

标签: java date jwt utc jjwt

我正在使用jjwt来创建jwt令牌。使用本地系统时间(即

)设置到期日期时,一切正常
  

日期expDate =新日期(新日期().getTime()+ 180000); //java.util.Date

但是我尝试使用UTC格式的日期时间,并用相同的3分钟到期日期对jwt令牌进行了签名。现在,即使我在创建令牌后也立即进行验证,但它正在抛出ExpiredJwtException。我正在使用SimpleDateFormat将时区设置为utc。 这是我在Java中使用jjwt创建令牌的代码:

    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) {
    }

令牌已成功创建。我也可以在线验证内容。 expDate比issDate早3分钟。我还通过传递已创建的令牌,在调用令牌后立即调用用于验证令牌的方法。 我的验证方法有:

    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");
    }

但是我得到ExpiredJwtException。错误是

  

jwt已过期:JWT已于2019-05-17T01:24:48Z过期。当前时间:   2019-05-17T07:06:48Z,相差20520836毫秒。允许的   时钟偏移:0毫秒。

从我的日志中,此时令牌中的颁发日期和到期日期是:

issued date is: 2019-05-17T07:06:48.000+0545
expiry date is: 2019-05-17T07:09:48.000+0545

这是怎么回事?谢谢您的帮助。

1 个答案:

答案 0 :(得分:5)

这里不需要SimpleDateFormat,因为Date代表自Unix Epoch以来的毫秒数,即1970年1月1日午夜(UTC)。

可能引起混淆的原因toString()方法,因为它在生成表示该值的字符串时会应用JVM的默认时区。

当您担心UTC时,请让我注意协调世界时(UTC)的实际含义:这是一个时间标准(而不是一个时区),这取决于高精度的原子钟和地球的自转。

UTC时间标准进行了多次调整,直到1972年才引入leap seconds,以使UTC与地球自转保持一致,这并不完全均匀,也没有原子钟精确。随着地球自转速度的降低,我们不时需要在这里和那里插入leap秒:

A graph from xkcd documenting the war between timekeepers and time

尽管Date的内部值旨在反映UTC,但由于这些leap秒,它可能无法完全反映出来。

Java 8和用于日期和时间的新API

即使Date符合UTC的需求,您也应避免。现在是旧类

Java 8引入了基于ISO日历系统的new API for dates, times, instants and durations。与Date最接近的等效项是Instant,它代表时间戳,即UTC时间轴上的时刻。

要捕获UTC中的当前时刻,可以使用以下命令:

Instant.now();              // Capture the current moment in UTC

您可以使用以下命令获取表示该值的字符串:

Instant.now().toString();   // 2019-05-17T12:50:40.474Z

此字符串的格式为ISO 8601,其中Z表示给定的时间以UTC为单位。

与JJWT的互操作性

对于与doesn't support类型为java.time的JJWT的互操作性,您可以从Date创建Instant的实例:

Date.from(Instant.now());   // Convert from modern class to legacy class

这是一个测试,该测试演示了如何发行和验证令牌:

@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));
}

对于上面的示例,我将JJWT 0.10.5与documentation中列出的依赖项一起使用。如果需要,上面的代码是用以下import语句编写的:

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;