token无感刷新

目录

无感刷新

方案一:双token

博客一:

博客二:

具体实现(上面第三种方式)

方案二:使用旧token获取新token


无感刷新

方案一:双token

博客一:

用户第一次用账号密码登录,服务器返回3个参数: access_token、refresh_token和expires_in(短token过期时间,这里返回7200),时效长短不一样。短的access_token 时效过了之后,发送时效长的 refresh_token 重新获取一个短时效token,如果都过期,就需要重新登录了。

refresh_token 就是用来刷新access_token 。
活跃用户的access_token 过期了,用refresh_token获取新的access_token。


// 对比时间,判断数据是否过期,created中执行
getTimeOut() {
    const localData = LocalCache.getCache('expires_in');
    if (localData) {
        const localDataObj = JSON.parse(localData);
        const nowTime = new Date().getTime();
        if (nowTime > localDataObj) {
            console.log('数据已过期');
            // 刷新token接口
            this.overdueLogin();
            return false;
        }
    }
},
// 刷新token
overdueLogin() {
    let self = this;
    let refresh_token = LocalCache.getCache('refresh_token');
    if (refresh_token) {
    	// 函数为封装刷新接口的post请求
        postOverdueLogin(refresh_token).then((res) => {
            if (res.code == 200 && “用户为活跃用户”) {
                //把获取的 access_token存入本地
                LocalCache.setCache(
                    'token',
                    res.access_token ? res.access_token : ''
                );
                // endtime
                LocalCache.setCache(
                    'expires_in',
                    res.expires_in
                        ? res.expires_in * 1000 + new Date().getTime()
                        : ''
                );
                console.log('刷新token成功');
            } else if(res.code !== 活跃用户){
                console.log('用户不是活跃用户,请重新登陆');
                // 逻辑...
            } else {
            	console.log('刷新失败,因为access_token和refresh_token均已过期,请重新登陆');
            	// 逻辑...
            }
        });
    }
},

原文链接 无感刷新token【优化技巧】_前端工程师请求出战的博客-CSDN博客_无感刷新token

博客二:

这里重点介绍这种方式,此方案的大致流程为:登录后客户端收到两个token(access_token,refresh_token),其中access_token用来鉴定身份,而refresh_token用来刷新access_token;这就要求了refresh_token要比access_token有效时间要长,并且refresh_token不能用来鉴定身份
使用这种方案又有一下解决方式:

1、后端每次响应都响应一个token过期时间,前端进行判断,在token过期前进行刷新,这种方式存在太多不可控因素,如客户端系统时间被修改、长时间没请求导致token过期却未刷新,无法做到无感刷新,并且也存在并发问题
2、使用定时器,使用定时器增加的资源的损耗,亏损了性能,不推荐
3、在得到token过期的请求时,再发送refresh_token;有点懒加载的意思,这种方案性能最优

具体实现(上面第三种方式)


流程

再理一理程序运行的流程:

  1. 首先,登录得到了两个token,并将其存起来
  2. 当access_token过期时,自动发送refresh_token到刷新token的请求路径请求token刷新
  3. 得到新的token之后,将请求重新发送,实现用户无感刷新token

代码

用到的工具函数

//判空
let isEmpty = function(obj) {
    return obj == null || obj == "undefined" || obj == "null" || new String(obj).trim() == '';
};
  1. 封装axios
    const my_axios = axios.create({
        baseURL: '/app',
        timeout: 15000,
        withCredentials: true
    });
    

这里对axios做一个简单的封装,不做过多赘述

        2.定义请求拦截器,将请求带上token

my_axios.interceptors.request.use(
        req => {
        	//判断当前是否存在tokenBo(tokenBo即两个token组成的对象),存在则带上token
        	//isEmpty函数在上方的工具函数中
            if (!isEmpty(sessionStorage.getItem("tokenBo"))) {
            	//通过请求路径中是否含有refershToken来判断当前是一般请求还是token刷新请求;从而在请求头中带上不同的token
                if (-req.url.indexOf("refreshToken") == 1) {
                    req.headers['accessToken'] = JSON.parse(sessionStorage.getItem("tokenBo")).accessToken;
                } else {
                    req.headers['refreshToken'] = JSON.parse(sessionStorage.getItem("tokenBo")).refreshToken;
                }
            }
            return req;
        },
        err => {
            return Promise.reject(err)
        }
    )

        3.响应拦截,进行token的无感刷新
        这是最难的一步,我们需要考虑到几个问题,第一:要实现无感,那么用户的请求就不能被舍弃,而是需要在得到新的token后帮他再执行一次;第二,当同时出现多个请求时,可能会导致多次刷新token的情况,所以需要用一个标志量来标志是否正在刷新token,并使用一个数据对请求进行存储

//标志当前是否正在刷洗token
let isNotRefreshing = true;
//请求队列
let requests = [];
my_axios.interceptors.response.use(
    async res => {
    	//我们可以定义一个标准响应体,比如:{code=10415,msg='token已过期',data:null},当收到token过期的响应就要进行token刷新了
        if (res.data.code == 10415) {
        	//首先拿到响应的配置参数,这和请求的配置参数是一样的,包括了url、data等信息,待会需要使用这个config来进行重发
            const config = res.config;
            //如果当前不处于刷新阶段就进行刷新操作
            if (isNotRefreshing) {
                isNotRefreshing = false;
                //返回刷新token的回调的返回值,本来考虑到由于请求是异步的,所以return会先执行,导致返回一个undefined,那么就需要使用async+await,但实际上没有加也成功了
                return my_axios.get("/admin/refreshToken")
                    .then(res => {
                    	//如果token无效或token仍然过期,就只能重新登录了
                        if (res.code == 10422 || res.code == 10415) {
                            sessionStorage.removeItem("tokenBo");
                            sessionStorage.removeItem("currentAdmin");
                            location.href = '/login';
                        } else if (res.code == 10200) {
                        	//刷新成功之后,将新的token存起来
                            sessionStorage.setItem("tokenBo", JSON.stringify(res.data))
                            //执行requests队列中的请求,(requests中存的不是请求参数,而是请求的Promise函数,这里直接拿来执行就好)
                            requests.forEach(run => run())
                            //将请求队列置空
                            requests = []
                            //重新执行当前未执行成功的请求并返回
                            return my_axios(config);
                        }
                    })
                    .catch(() => {
                        sessionStorage.removeItem("tokenBo");
                        sessionStorage.removeItem("currentAdmin");
                        location.href = '/';
                    })
                    .finally(() => {
                        isNotRefreshing = true;
                    })
            } else {
            	//如果当前已经是处于刷新token的状态,就将请求置于请求队列中,这个队列会在刷新token的回调中执行,由于new关键子存在声明提升,所以不用顾虑会有请求没有处理完的情况,这段添加请求的程序一定会在刷新token的回调执行之前执行的
                return new Promise(resolve => {
                	//这里加入的是一个promise的解析函数,将响应的config配置对应解析的请求函数存到requests中,等到刷新token回调后再执行
                    requests.push(() => {
                        resolve(my_axios(config));
                    })
                })
            }
        } else {
            if (res.data.code == 10200) {
                return res.data;
            } else {
                if (res.data.code == 10409) {
                    sessionStorage.removeItem("tokenBo");
                    sessionStorage.removeItem("currentAdmin");
                    location.href = "/#/login"
                }
                Message.error(res.data.message);
                return res.data;
            }
        }

    },
    err => {
        if (err && err.response && err.response.status) {
            switch (err.response.status) {
                case 404:
                    Message.error("页面未找到");
                    break;
                case 401:
                    Message.error('没有权限访问')
                    break;
                case 500:
                    Message.error("系统维护中")
                    break;
                case 505:
                    Message.error("网络错误")
            }
        }
    }
)

原文链接  
关于实现token无感刷新的解决方案_老蛙@的博客-CSDN博客_无感刷新

方案二:使用旧token获取新token

如果采取单个token的方式要实现token的自动刷新,就必须使用定时器,每隔一段时间自动刷新token,并且这个时候token一定要是没有过期的,因为如果已经过期的token也可以用来刷新,这和长期有效的token也没什么不同。但这种方式存在一定的问题:

为了保证同一时间,账户只被单个用户登录,后端必然要保证一个账户的多个token只有一个生效,最简单的方式就是使用分布式缓存中间件如redis,而存在并发请求时,可能前一个请求带着的是旧token,此时又到了刷新token的时间,就会产生请求的token与服务端存储的token不一致的问题
使用定时器是增加了性能的损耗,不是最佳的手段

还有一个 实现双token无感刷新_Andromeda_Y的博客-CSDN博客_双token实现

另一个  axios如何利用promise无痛刷新token - SegmentFault 思否

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值