一、项目场景
项目中需要使用到 JWT 来完成用户身份信息的认证
二、问题描述
使用 JWT 时设置过期时间无效
发现如果先调用 setExpiration 方法再调用 setClaims 方法,则会出现使用 JWT 设置过期时间无效的问题
Jwts.builder()
.setId(UUID.randomUUID().toString())
.setIssuedAt(new Date())
.compressWith(CompressionCodecs.GZIP)
.signWith(KEY, SignatureAlgorithm.HS512)
.setExpiration(new Date(currentTimeMillis + TOKEN_EXPIRATION_TIME_MILLIS))
.setClaims(claims)
.compact();
而如果先调用 setClaims 方法再调用 setExpiration 方法,则不会出现该问题
Jwts.builder()
.setId(UUID.randomUUID().toString())
.setIssuedAt(new Date())
.compressWith(CompressionCodecs.GZIP)
.signWith(KEY, SignatureAlgorithm.HS512)
.setClaims(claims)
.setExpiration(new Date(currentTimeMillis + TOKEN_EXPIRATION_TIME_MILLIS))
.compact();
三、原因分析
查看 setExpiration 方法的源码,当我们设置了过期时间时 exp != null ,这时会走到 ensureClaims().setExpiration(exp);
@Override
public JwtBuilder setExpiration(Date exp) {
if (exp != null) {
ensureClaims().setExpiration(exp);
} else {
if (this.claims != null) {
//noinspection ConstantConditions
this.claims.setExpiration(exp);
}
}
return this;
}
然后再查看 ensureClaims 方法的源码可以发现,如果我们没有事先设置 claims ,那么在这里会产生一个 DefaultClaims 对象赋值给 DefaultJwtBuilder 类的 claims 属性,这个对象将调用 setExpiration(exp) 方法设置过期时间
protected Claims ensureClaims() {
if (this.claims == null) {
this.claims = new DefaultClaims();
}
return this.claims;
}
但如果在这之后我们再调用 setClaims 方法,那么我们新设置的 claims 将重新赋值到 DefaultJwtBuilder 类的 claims 属性
@Override
public JwtBuilder setClaims(Map<String, ?> claims) {
this.claims = new DefaultClaims(claims);
return this;
}
此时新构造出来的 DefaultClaims 对象没有进行设置过期时间的操作,这也就导致了最后生成的 token 没有过期时间
另一方面,setId 方法和 setIssuedAt 方法的源码跟上面 setExpiration 的源码类似,所以 setId 方法和 setIssuedAt 方法也应该在调用 setClaims 方法后再进行调用
@Override
public JwtBuilder setId(String jti) {
if (Strings.hasText(jti)) {
ensureClaims().setId(jti);
} else {
if (this.claims != null) {
claims.setId(jti);
}
}
return this;
}
@Override
public JwtBuilder setIssuedAt(Date iat) {
if (iat != null) {
ensureClaims().setIssuedAt(iat);
} else {
if (this.claims != null) {
//noinspection ConstantConditions
this.claims.setIssuedAt(iat);
}
}
return this;
}
四、解决方案
生成 JWT 时应该最先调用 setClaims 方法再调用其他的 setXXX 方法
Jwts.builder()
.setClaims(claims)
.setId(UUID.randomUUID().toString())
.setIssuedAt(new Date())
.compressWith(CompressionCodecs.GZIP)
.signWith(KEY, SignatureAlgorithm.HS512)
.setExpiration(new Date(currentTimeMillis + TOKEN_EXPIRATION_TIME_MILLIS))
.compact();