在 UTC 中使用过期日期时 JWT 过期不起作用
Expiration of JWT not working when using expiration date in UTC
我正在使用 jjwt
创建 jwt 令牌。使用本地系统时间设置到期日期时一切正常,即
Date expDate = new Date(new Date().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
。错误是
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.
从我的日志来看,此时我的token中的签发日期和到期日期是:
issued date is: 2019-05-17T07:06:48.000+0545
expiry date is: 2019-05-17T07:09:48.000+0545
这是怎么回事?谢谢你的帮助。
不需要 SimpleDateFormat
here, as Date
represents the number of milliseconds since the Unix Epoch,即 1970 年 1 月 1 日午夜 (UTC)。
然而,可能会造成混淆的是 toString()
方法,因为它在生成表示该值的字符串时应用 JVM 的默认时区。
由于您担心 UTC,让我提醒您注意协调世界时 (UTC) 的实际含义:它是 时间标准(并且 不是时区),它是由高精度原子钟结合地球自转确定的。
UTC 时间标准经过多次调整,直到 1972 年才引入 leap seconds 以使 UTC 与地球自转保持一致,地球自转并不完全均匀,而且不如原子钟精确。由于地球的自转速度正在减慢,我们时不时地需要在这里或那里插入闰秒:
虽然 Date
的内部值旨在反映 UTC,但由于那些闰秒,它可能无法准确反映。
Java 8 和日期和时间的新 API
即使 Date
在 UTC 方面满足您的需求,您也应该 避免 它。现在是遗产class。
Java 8 基于 ISO 日历系统引入了 新的 API 用于日期、时间、时刻和持续时间。与 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 的互操作性
为了与 不支持 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;
我正在使用 jjwt
创建 jwt 令牌。使用本地系统时间设置到期日期时一切正常,即
Date expDate = new Date(new Date().getTime() + 180000); //java.util.Date
但我尝试使用 UTC 格式的日期时间并使用相同的 3 分钟到期日期签署了 jwt 令牌。现在它正在抛出 ExpiredJwtException
尽管我在创建令牌后立即进行验证。我正在使用 SimpleDateFormat 将时区设置为 utc。
这是我在 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) {
}
令牌创建成功。我也可以在线验证内容。 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
。错误是
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.
从我的日志来看,此时我的token中的签发日期和到期日期是:
issued date is: 2019-05-17T07:06:48.000+0545
expiry date is: 2019-05-17T07:09:48.000+0545
这是怎么回事?谢谢你的帮助。
不需要 SimpleDateFormat
here, as Date
represents the number of milliseconds since the Unix Epoch,即 1970 年 1 月 1 日午夜 (UTC)。
然而,可能会造成混淆的是 toString()
方法,因为它在生成表示该值的字符串时应用 JVM 的默认时区。
由于您担心 UTC,让我提醒您注意协调世界时 (UTC) 的实际含义:它是 时间标准(并且 不是时区),它是由高精度原子钟结合地球自转确定的。
UTC 时间标准经过多次调整,直到 1972 年才引入 leap seconds 以使 UTC 与地球自转保持一致,地球自转并不完全均匀,而且不如原子钟精确。由于地球的自转速度正在减慢,我们时不时地需要在这里或那里插入闰秒:
虽然 Date
的内部值旨在反映 UTC,但由于那些闰秒,它可能无法准确反映。
Java 8 和日期和时间的新 API
即使 Date
在 UTC 方面满足您的需求,您也应该 避免 它。现在是遗产class。
Java 8 基于 ISO 日历系统引入了 新的 API 用于日期、时间、时刻和持续时间。与 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 的互操作性
为了与 不支持 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;