前端无感刷新token

846 阅读3分钟

有人会说token刷新有什么意义?后端自动延长过期时间不就行了?这只是增加前端工作量而已!!!想到这,我已决定不在低调,“怒从心头起,恶向胆边生”,直接去找客户说道说道。其实我觉得token刷新还是有用的,万一token被人劫持了呢???所以我决定听客户这一次,实现token无感刷新这一需求(这和客户是1米8的健壮大哥没有半毛钱关系,哎,“万般皆是命,半点不由人”),经过一顿疯狂操作,牺牲寿命,终于把需求实现了。

思路

判断token是否过期,如已过期,则发起请求重新刷新

实现

import axios from "axios";
import router from "@/router";
import qs from 'qs';
​
//需要用到的方法
import { getRefreshToken, setLoginTime, getLoginTime, getExpiresTime } from "./oauth";
​
​
// 定义一个状态,用于判断token是否正在刷新,防止重复刷新
let isRefreshing = false;
// 在token刷新时,是否有别的请求,把它们保存起来,token刷新完后在重新调用
let refreshSubrequest = [];
​
// 创建axios 实例
const service = axios.create({
    baseURL: serviceConfig.commonConfig.baseURL,
    timeout: appConfig.timeout,
    headers: {
        "Content-Type": "application/jsonapplication/x-www-form-urlencoded"
    }
});
​
//再创建一个axios实例,单独调用token刷新的接口,防止和其他请求配置相互影响,有大佬知道别的方法可以告诉我..........
const serviceToken = axios.create({
    baseURL: serviceConfig.commonConfig.baseURL,
    timeout: appConfig.timeout,
    headers: {
        "Content-Type": "application/x-www-form-urlencoded;charset=UTF-8",
        "Authorization": "xxxxxxxxxxxx" //项目需要无token时,请求带特定的token,和token刷新没关系
    }
});
​
//由于功能写在响应拦截中,因此此处就不写具体的请求拦截器代码了
service.interceptors.request.use()
​
//响应拦截器 他来了,他来了
service.interceptors.response.use(response=>{
    //定义变量,用于判断token是否到过期时间
    //getExpiresTime方法为获取token有效期 
    //getLoginTime方法为获取上次token获取时间,在后端token失效时间的十分钟之前刷新token
    let expired = (new Date().getTime() - getLoginTime()) > (getExpiresTime - 10) * 60 * 1000;
    
    //在登录页不需要刷新token
    if (router.currentRoute.path !== "/Login") {
            if (expired) {
                //记录response配置,
                const config = response.config;
                //是否已在刷新中
                if (!isRefreshing) {
                    isRefreshing = true;
                    //发起token刷新请求,用的另一个实例
                    return serviceToken.post('/auth-svc/oauth/token',
                        qs.stringify( //qs为项目需要
                            {
                                grant_type: 'refresh_token',
                                refresh_token: getRefreshToken() 
                            }
                        )
                    ).then(res => {
                        const data = res.data.data;
                        const { token } = data;
                        //将请求来的信息存储到本地
                        for(let item in data){
                            window.sessionStorage.setItem(item,data[item]);
                        }
                        window.sessionStorage.setItem("token",'Bearer' + " " + data.access_token);
                        //更新token刷新时间
                        setLoginTime();
                        //项目需要token放在Authorization中
                        config.headers['Authorization'] = token;
                        config.baseURL = '';
                        //将储存的请求执行
                        refreshSubrequest.forEach(item => item(token));
                        //清空请求
                        refreshSubrequest = [];
    
                        return service(config);
                    }).catch(res => {
                        console.error('refreshtoken error =>', res);
                        router.replace({
                            path: '/Login'
                        })
                    }).finally(() => {
                        isRefreshing = false;
                    });
                } else {
                    // 正在刷新token,返回一个未执行resolve的promise
                    return new Promise((resolve) => {
                    // 将resolve放进队列,用一个函数形式来保存,等token刷新后直接执行
                        refreshSubrequest.push((token) => {
                            config.baseURL = '';
                            config.headers['Authorization'] = token;
                            resolve(service(config));
                        });
                    });
                }
            }
        }
    
    
    return response
})

所用到的方法

const ExpiresKey = 'expires_in'; //注意: 此时间为token有效时长
const TokenTypeKey = 'bearer';
const RefreshTokenKey = 'refresh_token';
const loginTimeKey = "loginTime";
​
/**
 * 获取刷新token的参数
 * @returns 刷新token的参数
 */
export function getRefreshToken () {
    return sessionStorage.getItem(RefreshTokenKey);
}
​
/**
 * 保存登录时间戳
 */
export function setLoginTime(){
    const loginTime = new Date().getTime();
    sessionStorage.setItem(loginTimeKey,loginTime);
}
​
/**
 * 获取上次登录时间戳
 * @returns 上次登录时间戳
 */
export function getLoginTime(){
    return sessionStorage.getItem(loginTimeKey);
}
​
/**
 * 获取token有效期
 * @returns token有效期
 */
export function getExpiresTime(){
    return sessionStorage.getItem(ExpiresKey);
}