📜  jwt 颤振刷新令牌 (1)

📅  最后修改于: 2023-12-03 15:17:07.240000             🧑  作者: Mango

JWT 颤振刷新令牌

在使用 JWT(JSON Web Token)进行身份认证时,通常需要使用到刷新令牌来保证用户的持续认证。在实际应用中,为了防止刷新令牌被恶意获取或复用,我们一般使用颤振刷新令牌(Jitter Refresh Token)的方式来保证刷新令牌的安全性。

什么是 JWT?

JWT 是一种基于 JSON 的开放标准(RFC 7519),用于在网络上安全传输信息。它通过对 JSON 数据进行签名或加密,来保证信息的可靠性和机密性。

一个 JWT 通常由三个部分组成:头部、载荷和签名。

JWT 头部

JWT 头部包含了两个主要的部分:声明类型和签名算法。例如,下面是一个使用 HS256 算法签名的 JWT 头部:

{
  "alg": "HS256",
  "typ": "JWT"
}
JWT 载荷

JWT 载荷是 JWT 中包含实际数据的部分。它通常包含了一些标准的声明(例如 issuer、subject、audience 等)和一些自定义的声明。

例如,下面是一个包含了用户 ID 和角色的 JWT 载荷:

{
  "sub": "1234567890",
  "name": "John Doe",
  "admin": true
}
JWT 签名

JWT 签名用于验证 JWT 的真实性和完整性。它通常是由头部和载荷生成的,并使用私钥(对称加密)或公钥(非对称加密)进行签名。

例如,下面是一个使用私钥对 JWT 签名的示例:

HMACSHA256(
  base64UrlEncode(header) + "." +
  base64UrlEncode(payload),
  secret)
什么是颤振刷新令牌?

颤振刷新令牌是一种滑动窗口的刷新令牌,它的有效期是随机的,并且每次使用时都会重新生成新的有效期。这样可以避免刷新令牌因长时间未使用而被恶意获取或复用。

例如,下面是一个使用颤振刷新令牌的 JWT:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyLCJleHAiOjE1MTYyOTkwMjIsImF1ZCI6InJlZ2lzdGVyIiwiaXNzIjoic2VydmljZSJ9.Ow28P8OenMhb5ZRVf5l5zWb5TIKvF9QyIJJSnBCcvVw

其中,有效期 exp 是随机生成的,并在每次使用时重新生成。

如何实现颤振刷新令牌?

在实现颤振刷新令牌时,我们通常可以使用一个自定义 Claims,例如:

public class JitterClaims extends DefaultClaims {
    public long jitterTime(long jitterSeconds) {
        return (long) (System.currentTimeMillis() / 1000) + ThreadLocalRandom.current().nextLong(-jitterSeconds, jitterSeconds);
    }

    public void setExpiration(long jitterSeconds) {
        super.setExpiration(new Date(jitterTime(jitterSeconds) * 1000));
    }

    public void setNotBefore(long jitterSeconds) {
        super.setNotBefore(new Date(jitterTime(jitterSeconds) * 1000));
    }
}

这个自定义 Claims 可以用于生成颤振刷新令牌,例如:

public String generateToken(String subject, long jitterSeconds, String secret) {
    JitterClaims claims = new JitterClaims();
    claims.setSubject(subject);
    claims.setExpiration(jitterSeconds);
    claims.setNotBefore(jitterSeconds);
    return Jwts.builder()
            .setClaims(claims)
            .signWith(SignatureAlgorithm.HS256, secret)
            .compact();
}

在解析颤振刷新令牌时,我们也需要实现一个自定义 ClaimsResolver,例如:

public class JitterClaimsResolver implements ClaimsResolver {
    private final long jitterSeconds;

    public JitterClaimsResolver(long jitterSeconds) {
        this.jitterSeconds = jitterSeconds;
    }

    @Override
    public Claims resolveClaims(String token) {
        return Jwts.parser()
                .setSigningKey(secret)
                .parseClaimsJws(token)
                .getBody();
    }

    @Override
    public Claims resolveJitterClaims(Claims claims) {
        long exp = (long) claims.getExpiration().getTime() / 1000;
        long nb = (long) claims.getNotBefore().getTime() / 1000;
        if (exp <= nb) {
            throw new JwtException("Invalid jitter token: empty window");
        }
        if (System.currentTimeMillis() / 1000 < nb) {
            throw new JwtException("Invalid jitter token: too early");
        }
        if (exp - nb < jitterSeconds) {
            throw new JwtException("Invalid jitter token: too short window");
        }

        JitterClaims jitterClaims = new JitterClaims();
        jitterClaims.putAll(claims);
        jitterClaims.setExpiration(jitterSeconds);
        jitterClaims.setNotBefore(jitterSeconds);
        return jitterClaims;
    }
}

在使用颤振刷新令牌时,我们需要先使用 resolveClaims 方法解析出 JWT 的原始 Claims,然后使用 resolveJitterClaims 方法生成带有颤振效果的 Claims,例如:

public String refreshToken(String token, long jitterSeconds, String secret) {
    JitterClaims claims = (JitterClaims) claimsResolver.resolveJitterClaims(claimsResolver.resolveClaims(token));
    return Jwts.builder()
            .setClaims(claims)
            .signWith(SignatureAlgorithm.HS256, secret)
            .compact();
}

这样,我们就可以使用颤振刷新令牌来保证 JWT 的安全性了。

参考资料