前言 在互联网的登陆系统中一般有session cookie 和 jwt token来进行验证用户是否登录的.下面来说一下关于 jwt的坑:

1.首先登录成功的返回,其中jwt的有效期为2小时,refreshJwt的有效期为30天如下:

2.保存jwt 和refreshJwt 在cookie中,当然这里不一定要保存在cookie中,也可以保存在别的地方.这里以cookie为例子:

 3.核心思路:当jwt过期时(超过了2小时),调用后端接口会返回401的状态码(这个时候代表jwt已经过期了,需要刷新了),再拦截器中拦截响应的数据,这个时候就需要拿refreshJwt去调用后端的刷新接口,把获取到的最新jwt覆盖掉本地的jwt和refreshJwt,然后再次进行刚才的请求,并且把获取到的数据,从响应拦截器中返回回去.

步骤大概如此: 前端发起ajax请求 => 后端发现jwt已经过期,返回401状态码 => 前端拦截响应数据,并发起刷新token的请求 => 拿到最新的jwt和refresh,保存到本地 => 拿到最新的jwt去进行刚刚未请求成功的接口 => 获取到刚刚请求的结果,覆盖第一次请求失败(状态码为401)的响应数据 => 返回第二次请求的结果.   

另一种情况就是:前端发起ajax请求 => 后端发现jwt已经过期,返回401状态码 => 前端拦截响应数据,并发起刷新token的请求 =>后端返回了410的状态码(这个时候代表refreshJwt也过期了,需要进行重新登录了) => 真正的过期了,需要跳转到登录界面.  

这样刷新token是在用户无感的情况下进行操作的.

下面进行贴代码了:

此拦截器模块为http,封装的步骤参考:https://blog.csdn.net/qq_33270001/article/details/86612528  注:原来的http模块中并未封装refreshJwt的操作.

3. 新建一个refreshToken.js的文件:

看核心操作就是,util.getCookie(`REFRESHJWT-COLLECTOR`); 这些都是封装来操作cookie的工具.无需重点关注

import axios from 'axios';
import conf from '@config';
import util from '@util';
import { BASE_URL, API_SERVER_FILTER } from './models/types';
export default async() => {
    try {
        const refreshToken = util.getCookie(conf.REFRESH_AUTHTOKEN_STORE_KEY);
        const { data } = await axios.post(`${BASE_URL}${API_SERVER_FILTER}/user/reftoken`, {
            token: util.getCookie(conf.AUTHTOKEN_STORE_KEY),
            platform: 'PC',
            refToken: refreshToken
        });
        const { code, data: { token, refToken } } = data || {};
        if (token && refToken) {
            util.setCookie(conf.AUTHTOKEN_STORE_KEY, token); //jwt
            util.setCookie(conf.REFRESH_AUTHTOKEN_STORE_KEY, refToken); //刷新token
        }
        return { code, data: { token } };
    } catch (error) {
        console.log(error);
    }
};

4. 拦截器配置如下:

 

/***
 * Created by Simple on 2018/1/14 0014.
 * Http请求控制器模块
 */

import http from 'axios';
import { Loading, Message } from 'element-ui';
import router from '@/router';
import conf from '@config';
import util from '@util';
import refreshToken from './refreshToken';
const instance = http.create();
// instance 配置
instance.defaults.timeout = 1000 * 30;
instance.defaults.baseURL = process.env.NODE_ENV === 'production' ? conf.productionUrl : conf.devUrl;
instance.setToken = (token) => {
    instance.defaults.headers.Authorization = token;
    util.setCookie(conf.AUTHTOKEN_STORE_KEY, token); //jwt
}

// 配置通用请求动画
let loading = null;
// 是否正在刷新的标记
let isRefreshing = false;
// 重试队列,每一项将是一个待执行的函数形式
let requests = [];

/**
 * 通用请求拦截配置
 * @param {*} config
 */
const instanceConf = (config) => {
    //===========================签名 S=====================================
   const { noncestr, timestamp, sign } = util.getSign(config);
    config.headers.noncestr = noncestr;
    config.headers.timestamp = timestamp;
    config.headers.sign = sign;
    //===========================签名 E=====================================
    config.headers.platform = `PC`;
    //===========================移除重置空字符串 S=====================================
    util.resetParamsEmpty(config);
    //===========================移除重置空字符串 E=====================================
    config.headers.Authorization = util.getCookie(conf.AUTHTOKEN_STORE_KEY);
    if (config.url.indexOf('/user/gqrcstatus') == -1) {
        loading = Loading.service({
            lock: true,
            text: '拼命加载中...',
            background: 'rgba(255, 255, 255, .8)',
        });
    }
    return config;
}

instance.interceptors.request.use(instanceConf, err => {
    if (loading && err) loading.close();
    return Promise.reject(err);
});

// http response 拦截器
instance.interceptors.response.use(async (response) => {
    let data = {};
    if (response && response.data) {
        let code = Number(response.code || response.data.code)
        data = response.data;
        if (code === 200) {
            data = response.data;
        }else if(code == 402){
            if(!isRefreshing){
                isRefreshing = true;
                try {
                    const { data: { token } } = await refreshToken();
                    if(token){
                        instance.setToken(token);
                        response.config.headers.Authorization = token;
                        // 已经刷新了token,将所有队列中的请求进行重试
                        requests.forEach(cb => cb(token));
                        requests = [];
                        return instance(instanceConf(response.config));
                    }
                } catch (error) {   //刷新时候直接判断token 不用判断code
                    console.error('refreshtoken error =>', error);
                    routerRedirect({ redirect: router.currentRoute.fullPath });
                } finally {
                    isRefreshing = false;
                }
            }else{
                // 正在刷新token,将返回一个未执行resolve的promise
                return new Promise((resolve) => {
                    // 将resolve放进队列,用一个函数形式来保存,等token刷新后直接执行
                    requests.push((token) => {
                        response.config.headers.Authorization = token;
                        resolve(instance(instanceConf(response.config)));
                    });
                });
            }
        }  else if (code === 401) {
            routerRedirect({ redirect: router.currentRoute.fullPath });
        } else if(![30073,30072,30074,30078,30075,30076].includes(code)) {
            Message.error(response.data && response.data.message || '网络连接出错!请稍后刷新重试!');
        }
    }
    if (loading) loading.close();
    return data;
}, (error) => {
    console.log(`object`, error);
    if (loading) loading.close();
    Message.error('哎呀~ (ಥ﹏ಥ)网络又开小差了,请稍后刷新重试!');
    return Promise.reject(error.response.data);
});

/**
 * 重定向
 */
const routerRedirect = ({ path = '/login', redirect })=>{
    Message.warning(`身份过期,请重新登录!`);
    if (router.currentRoute.path != '/login') {
        setTimeout(() => {
            router.replace({ path, query: { redirect } });
        }, 1200);
    }
}

export default instance;

5.  操作演示:

已经过期了,需要进行刷新操作

进行刷新覆盖操作 

 

重新进行请求,并覆盖掉原来的

大功告成,小生的刷新token思路如此,如阁下有更好的思路,方便分享,请留言哦,刷新token已经可以了.有问题的小伙伴请求留言.

以前写了一篇发现在并发处理的时候有问题:

然后参考了一部分资料修改了代码,完美解决.

参考资料:

https://segmentfault.com/a/1190000020986592 

Logo

前往低代码交流专区

更多推荐