You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Spring Boot集成JWT认证遇过期错误,请求排查问题

问题

在Spring Boot/Security项目中首次使用JWT认证,此前JWT的生成及请求头接收均正常。但编写完从token提取用户名的相关代码后,使用凭证登录时出现如下错误:

JWT expired 13729211 milliseconds ago at 2024-07-07T14:41:48.000Z. Current time: 2024-07-07T18:30:37.211Z. Allowed clock skew: 0 milliseconds.

JWT工具类JwtService代码如下:

@Component
public class JwtService {

    private final String SECRET_KEY = "eaeb7fefd93b6a53da9dd86b6b228e1f8a13f355b57279141093d6c63c29f4e0";

    public String generateToken(String username) {
        Map<String, Object> claims = new HashMap<>();
        return createToken(username, claims);
    }

    private String createToken(String username, Map<String, Object> claims) {
        return Jwts.builder()
                .setClaims(claims)
                .setSubject(username)
                .setIssuedAt(new Date(System.currentTimeMillis()))
                .setExpiration(new Date(System.currentTimeMillis() + 1000 * 60 * 60))
                .setHeaderParam("typ", "JWT")
                .signWith(getSignKey(), SignatureAlgorithm.HS256)
                .compact();
    }

    private Key getSignKey() {
        byte[] keyBytes = Decoders.BASE64.decode(SECRET_KEY);
        return Keys.hmacShaKeyFor(keyBytes);
    }

    // extract the username from the token
    public String extractUserName(String token) {
        return extractClaim(token, Claims::getSubject);
    }

    public <T> T extractClaim(String token, Function<Claims, T> claimsResolver) {
        final Claims claims = extractAllClaims(token);
        return claimsResolver.apply(claims);
    }

    private Claims extractAllClaims(String token) {
        return Jwts.parser().setSigningKey(getSignKey())
                .build()
                .parseClaimsJws(token)
                .getBody();
    }

    // to check expiration of token
    public Date extractExpiration(String token) {
        return extractClaim(token, Claims::getExpiration);
    }

    // to check token is expired or not
    public boolean isTokenExpired(String token) {
        return extractExpiration(token).before(new Date());
    }

    public boolean validateToken(String token, UserDetails userDetails) {
        final String username = extractUserName(token);
        return (username.equals(userDetails.getUsername()) && !isTokenExpired(token));
    }
}

代码中设置token过期时间为1小时,尝试延长该时间但错误依旧,作为JWT新手,缺乏日志调试经验,需要排查问题。

排查与解决方案
  • 核对服务器与客户端的时间一致性
    错误里token过期时间和服务器当前时间差了近4小时,核心原因大概率是服务器和本地客户端的系统时间不同步(时区错配、时间校准失效)。JWT的过期校验完全依赖服务器本地时间,只要服务器时间比token过期时间晚,就会判定过期。先把服务器和客户端的系统时间同步到正确的UTC或本地时区,再测试。

  • 确认请求携带的是全新生成的token
    检查前端是否携带了旧的过期token(比如浏览器缓存、本地存储的token未更新)。可以在generateToken方法里加日志,输出生成的token过期时间,再和请求时解析token得到的过期时间对比,确认两者是同一个token。

  • 给JWT解析器添加时钟偏差容忍度
    如果存在不可避免的微小时间差,可配置解析器允许一定的时钟偏差,避免几秒误差导致误判。修改extractAllClaims方法:

    private Claims extractAllClaims(String token) {
        return Jwts.parser()
                .setSigningKey(getSignKey())
                .setAllowedClockSkewSeconds(60) // 允许60秒的时钟偏差
                .build()
                .parseClaimsJws(token)
                .getBody();
    }
    
  • 验证token生成逻辑的时间计算
    在createToken方法里打印过期时间的具体值,确认计算逻辑正确:

    Date expirationDate = new Date(System.currentTimeMillis() + 1000 * 60 * 60);
    System.out.println("生成的Token过期时间:" + expirationDate);
    

    确保没有误用System.nanoTime()这类错误的时间源,或者计算时出现单位转换错误。

  • 检查登录接口的token返回逻辑
    确认登录成功后,返回给客户端的是刚生成的新token,而不是之前缓存的旧值。可以在登录接口的返回步骤里,打印返回的token及其过期时间,和客户端实际收到的内容做对比。

内容的提问来源于stack exchange,提问作者Avijit Patra

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.21 11:40:12