zoukankan      html  css  js  c++  java
  • 浅析JWT中token过期后解析报错ExpiredJwtException的解决及过期之后如何进行后续业务处理

    一、问题背景

      最近搭建springcloud的项目,项目采取了Jwt + spring security 来进行登录验证,Jwt token 锁定用户的失效时间,但是由于 jwt token特性导致token失效时间无法刷新,所以必须新创建一个token令牌,用来代替之前已失效token。

      (token失效时间无法刷新的原因是由于jwt创建token是根据jwt保存的相关信息来计算的,过期时间是其中的一个计算维度,所以一旦过期时间改了,那么生成的token值也就变了。)

      之后为了解决这个问题,结合了redis,将token值保存到redis中,用户操作后刷新redis的有效时间,这样如果jwt token失效了,再检查 redis 中保存token的key是否失效,如果没有失效,那么就重新创建jwt token ,失效了,就重新登录。

      保存在redis中的 key 是用户名, 但是我需要把 jwt token 转化后从 claims 中取出这个用户名,一开始我直接转化,进行debug的时候发现如果token超时了,jwt 没有返回转化结果, 而是直接抛出了异常,我查看JWT所有的转化方法,发现Jwt所有的转化最终处理都是parse(claimJws)这个方法,而这个方法正是我一开始用的解析方法。

      原本是调用jwtUtil(jwt的工具类),传入一个token,判断是否过期,然而却莫名其妙得抛异常了,而业务中还需要根据是否过期进行后续逻辑!异常如下:

    io.jsonwebtoken.ExpiredJwtException: JWT expired at 2020-07-29T14:48:14Z. Current time: 2020-07-29T14:48:50Z, a difference of 36843 milliseconds.  Allowed clock skew: 0 milliseconds.
        at io.jsonwebtoken.impl.DefaultJwtParser.parse(DefaultJwtParser.java:385)
        at io.jsonwebtoken.impl.DefaultJwtParser.parse(DefaultJwtParser.java:481)
        at io.jsonwebtoken.impl.DefaultJwtParser.parseClaimsJws(DefaultJwtParser.java:541)
        at com.smart.util.JwtUtil.parseJwt(JwtUtil.java:63)
        at com.smart.util.JwtUtil.isTokenExpired(JwtUtil.java:93)

    二、问题原因

      根据报错堆栈信息找到了DefaultJwtParser类中,找到了问题的原因。

    boolean allowSkew = this.allowedClockSkewMillis > 0L;
    if (claims != null) {
        Date now = this.clock.now();
        long nowTime = now.getTime();
        Date exp = claims.getExpiration();
        String nbfVal;
        SimpleDateFormat sdf;
        if (exp != null) {
            long maxTime = nowTime - this.allowedClockSkewMillis;
            Date max = allowSkew ? new Date(maxTime) : now;
            if (max.after(exp)) {
                sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'");
                String expVal = sdf.format(exp);
                nbfVal = sdf.format(now);
                long differenceMillis = maxTime - exp.getTime();
                String msg = "JWT expired at " + expVal + ". Current time: " + nbfVal + ", a difference of " + differenceMillis + " milliseconds.  Allowed clock skew: " + this.allowedClockSkewMillis + " milliseconds.";
                throw new ExpiredJwtException((Header)header, claims, msg);
            }
        }
        ......
    }

      看到结尾的throw new ExpiredJwtException,我相信就找到了问题的关键,原来在在解析token并发现这个token已经过期了,它作出的反应是直接抛异常。

      异常定义的构造方法中除了msg信息,还有claims和header信息。

      检查claims发现,在异常之前token其实已经解析完毕。

      这样也就代表着,抛出的这个异常 ExpiredJwtException 中有一个参数 claims 就是解析后的token,那么本次这个问题也就解决了。

      catch ExpiredJwtException 异常后,直接从异常中获取解析的数据即可,如下介绍。

    三、过期报错了,如何进行后续业务处理

      回到我们的工具类中的解析jwt的方法:

    public Claims parseJwt(String token){
        Claims claims = Jwts.parser()
                    .setSigningKey(signKey) // 设置标识名
                    .parseClaimsJws(token)  //解析token
                    .getBody();
        return claims;
    }

      改为:不管是否过期都返回 claims 对象

    //不管是否过期,都返回claims对象
    public Claims parseJwt(String token){
        Claims claims;
        try {
            claims = Jwts.parser()
                    .setSigningKey(signKey) // 设置标识名
                    .parseClaimsJws(token)  //解析token
                    .getBody();
        } catch (ExpiredJwtException e) {
            claims = e.getClaims();
        }
        return claims;
    }

      可以从异常中找到这个过期的claim对象信息;判断token是否过期的方法我也相应修改了,如下:

    public Boolean isTokenExpired(String token) {
      //不管是否过期,都返回claims对象
      Claims claims = this.parseJwt(token);
      Date expiration = claims.getExpiration();
      //和当前时间进行对比来判断是否过期
      return new Date(System.currentTimeMillis()).after(expiration);
    }

    参考文章:

    https://blog.csdn.net/qq_38294335/article/details/107669630

    https://blog.csdn.net/Piteover/article/details/90676279

  • 相关阅读:
    3月1日起执行!江苏居民阶梯电价有变化!
    个体工商户需要报税吗?
    西红柿的选购方法
    正常人一天该走六千还是一万步?步数滚蛋,运动强度和时长才重要,锻炼身体,快走
    NAT四种类型以及提高NAT类型的途径和方法 nat1 nat2 nat3 nat4
    6种沙坦类药物有什么区别, 选哪一种更好? 药师一次说清楚
    心脏神经官能症是什么症状
    呼吸性碱中毒
    Python Serverless 开源框架:Zappa(详细教程)
    不用代码趣讲 ZooKeeper 集群
  • 原文地址:https://www.cnblogs.com/goloving/p/14922929.html
Copyright © 2011-2022 走看看