如何做到无感刷新token?

戳上方蓝字“Java笔记虾”关注我

为什么需要无感刷新Token?

最近浏览到一个文章里面的提问,是这样的:

当我在系统页面上做业务操作的时候会出现突然闪退的情况,然后跳转到登录页面需要重新登录系统,系统使用了Redis做缓存来存储用户ID,和用户的token信息,这是什么问题呢?

解答:

突然闪退,一般都是由于你的token过期的问题,导致身份失效。

解决方案:
  • 自动刷新token
  • token续约
思路

如果Token即将过期,你在验证用户权限的同时,为用户生成一个新的Token并返回给客户端,客户端需要更新本地存储的Token,还可以做定时任务来刷新Token,可以不生成新的Token,在快过期的时候,直接给Token增加时间

后端刷新Token方案 — 自动刷新token

自动刷新token是属于后端的解决方案,由后端来检查一个Token的过期时间是否快要过期了,如果快要过期了,就往请求头中重新放一个token,然后前端那边做拦截,拿到请求头里面的新的token,如果这个新的token和老的token不一致,直接将本地的token更换

接下来拿代码举例子

先引入依赖

<dependency>
    <groupId>cn.hutool</groupId>
    <artifactId>hutool-all</artifactId>
    <version>5.5.1</version>
</dependency>
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>fastjson</artifactId>
    <version>1.2.33</version>
</dependency>
<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt</artifactId>
    <version>0.9.1</version>
</dependency>

这是一个生成token的例子

import io.jsonwebtoken.Claims;
import io.jsonwebtoken.JwtBuilder;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;

import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import java.util.Base64;
import java.util.Date;
import java.util.UUID;

public class JwtUtil {

    // 有效期为
    public static final Long JWT_TTL = 60 * 60 * 1000 * 24;// 60 * 60 * 1000 * 24  一个小时
    // 设置秘钥明文 --- 自己改就行
    public static final String JWT_KEY = "qx";
   // 用于生成uuid,用来标识唯一
    public static String getUUID(){
        String uuid = UUID.randomUUID().toString().replaceAll("-""");//token用UUID来代替
        return uuid;
    }

    /**
        id      : 标识唯一
        subject : 我们想要加密存储的数据
        ttl     : 我们想要设置的过期时间
     */


   // 生成token  jwt加密 subject token中要存放的数据(json格式)
    public static String createJWT(String subject) {
        JwtBuilder builder = getJwtBuilder(subject, null, getUUID());// 设置过期时间
        return builder.compact();
    }

    // 生成token  jwt加密
    public static String createJWT(String subject, Long ttlMillis) {
        JwtBuilder builder = getJwtBuilder(subject, ttlMillis, getUUID());// 设置过期时间
        return builder.compact();
    }
  
    // 创建token jwt加密
    public static String createJWT(String id, String subject, Long ttlMillis) {
        JwtBuilder builder = getJwtBuilder(subject, ttlMillis, id);// 设置过期时间
        return builder.compact();
    }

    private static JwtBuilder getJwtBuilder(String subject, Long ttlMillis, String uuid) {
        SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;
        SecretKey secretKey = generalKey();
        long nowMillis = System.currentTimeMillis();
        Date now = new Date(nowMillis);
        if(ttlMillis==null){
            ttlMillis=JwtUtil.JWT_TTL;
        }
        long expMillis = nowMillis + ttlMillis;
        Date expDate = new Date(expMillis);
        return Jwts.builder()
                .setId(uuid)              //唯一的ID
                .setSubject(subject)   // 主题  可以是JSON数据
                .setIssuer("sg")     // 签发者
                .setIssuedAt(now)      // 签发时间
                .signWith(signatureAlgorithm, secretKey) //使用HS256对称加密算法签名, 第二个参数为秘钥
                .setExpiration(expDate);
    }
    
    // 生成加密后的秘钥 secretKey
    public static SecretKey generalKey() {
        byte[] encodedKey = Base64.getDecoder().decode(JwtUtil.JWT_KEY);
        SecretKey key = new SecretKeySpec(encodedKey, 0, encodedKey.length, "AES");
        return key;
    }


    // jwt解密
    public static Claims parseJWT(String jwt) throws Exception {
        SecretKey secretKey = generalKey();
        return Jwts.parser()
                .setSigningKey(secretKey)
                .parseClaimsJws(jwt)
                .getBody();
    }
}

写个单元测试,测试一下

@Test
void test() throws Exception {
    String token = JwtUtil.createJWT("1735209949551763457");
    System.out.println("Token: " + token);
    Date tokenExpirationDate = getTokenExpirationDate(token);
    System.out.println(tokenExpirationDate);
    System.out.println(tokenExpirationDate.toString());
    long exp = tokenExpirationDate.getTime();
    long cur = System.currentTimeMillis();
    System.out.println(exp);
    System.out.println(cur);
    System.out.println(exp - cur);
}

// 解析令牌并获取过期时间
public static Date getTokenExpirationDate(String token) {
    try {
        SecretKey secretKey = generalKey();
        Claims claims = Jwts.parser()
                .setSigningKey(secretKey)
                .parseClaimsJws(token)
                .getBody();
        return claims.getExpiration();
    } catch (ExpiredJwtException | SignatureException e) {
        throw new RuntimeException("Invalid token", e);
    }
}

Token有点长,就不放全部了

如何做到无感刷新token?

可以看到我们的 exp 过期时间的毫秒数为 1703651262000

可以看到我们的 cur  当前时间的毫秒数为 1703564863035

我们将两者相减得到的值为 86398965ms ,我们可以算一下一天的毫秒数是多少 1000 * 60 * 60 * 24 ms = 86400000ms

这样我们就能够拿到token的过期时间tokenExpirationDate了

我们就可以通过在校验token的时候,如果token校验通过了,此时我们拿到该token的过期时间,以(过期时间 – 当前时间)进行判断

如果说 (过期时间 – 当前时间) 小于约定的值,那么我们就重新根据token里面的信息,重新创建一个token,将新的token放到请求头中

返回给前端,前端去进行本地存储更新token

前端刷新Token方案 — token续约

token的续约偏向于前端的解决方案,即由前端来进行token的过期时间的判断,首先前后端需要对接商量好一个token续约的接口,当前端发现这个token快要过期的时候,向后端发送该token,然后后端将该token的过期时间延长。

前端采用的是双Token的方式,access-token 和 refresh-token即 AT 和 RT

而对于纯后端的方式,就是只有access-token这一个token

那么问题来了 AT 和 RT 到底有什么区别?为什么需要RT?

在前端实现方案来说,RT是用来在AT即将过期的时候,用RT获取最新的token

我解释一下我的观点:

AT的暴露机会更多,每个请求都要携带,所以设置的过期时间短一点,减少劫持风险

RT只会暴露在auth服务中用来刷新at,设置的过期时间长一点,增加便利性。

AT 和 RT 是为了网络传输安全,网络传输中,容易暴露 AT,因为 AT 时间短,暴露后风险系数才低

这种是标准的安全处理,其实已经无需探讨他的合理性,就好像 https 之于 http 一样

疑问及思考

要是前端有一个表单页面,长时间不进行请求的发送,此时用户填写完表单了,再点击提交的时候,后端返回401了,怎么办?

也就是说,虽然你后端可以无感刷新Token,但是你后端无感刷新Token的前提是:前端得发请求,如果用户长时间不进行页面的交互,即没有进行任何业务逻辑的跳转什么的,就单纯的往表单上面填东西,什么请求也没发的情况下,后端是无法感知Token过期的

这种情况怎么解决?

  • 对于纯后端的解决方案,我是这样想的

让前端在表单填写内容的时候做处理,如果提交返回的是401,那么前端就需要获取表单存在本地存储 然后跳转登录页,登录成功后返回这个页面,然后从本地存储取出来再回显到表单上面。

  • 对于前端的解决方案,我是这样想的

对于后端来说就是AT过期了,而对于前端来说就是AT和RT都过期了,怎么处理?

  1. 需要监听refresh token的过期时间,在接近过期的时候向后端发起请求来刷新refresh token 或者是定期刷新一下refresh token
  2. 和后端的解决方案一样,前端做一个类似草稿箱的功能对表单等元素进行保存

感谢阅读,希望对你有所帮助 :)   来源:

juejin.cn/post/7316797749517631515

后端专属技术群

构建高质量的技术交流社群,欢迎从事编程开发、技术招聘HR进群,也欢迎大家分享自己公司的内推信息,相互帮助,一起进步!

文明发言,以交流技术职位内推行业探讨为主

广告人士勿入,切勿轻信私聊,防止被骗

如何做到无感刷新token?

加我好友,拉你进群

原文始发于微信公众号(Java笔记虾):如何做到无感刷新token?

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/198662.html

(0)
小半的头像小半

相关推荐

发表回复

登录后才能评论
极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!