axios实现无感知刷新token

项目背景

最近做了一个oa类的web项目,用户需要登录后才能正常访问页面。当用户登录成功后,后端接口会返给前端一个token,之后的每一次接口调用都需要携带token,服务端验证这个token来判断用户是否已经成功登录。当然token是由时效性的,当token过期时,我们可以重新登录来获取新的token,但这样做体验会很差,所以要求我们利用旧的token去换取新的token。也就是说我们要调用一个刷新token的接口,将之前的token传给服务端来换取最新的token,有了最新的token之后,我们再次调用之前因为token过期返回状态接口,这样就做到用户无感知刷新token了。

方案讨论

利用axios中的axios.interceptors.response.use()接口请求后拦截,当token过期时,我们通过调用刷新token方法获取最新的token之后,携带最新的token再重新进行一次请求。一般一个页面的展示数据都需要调用多个接口,如果token过期了,每个接口都调用一遍刷新token,这样势必会造成资源浪费,我们希望的是如果token过期了,我们只调用一次刷新token接口。这里我的解决方案是定义一个let isRefreshing = false;标签变量,标记当前是否正在刷新token的状态,如果正在刷新则不再调用刷新token的接口。这里还有一个难点需要解决,就是多个接口同时发起请求时,假设第一个接口先进入,接着执行刷新token接口,因为标签变量的原因,其它接口不会执行刷新token,我们需要将剩下的接口放入到一个队列中,当刷新token接口执行完毕之后,再依次执行被放入到队列中的接口,这里我们需要用到Promise将所有未执行的resolve放入到一个数组中,数组中每一个元素都处于pending状态,当刷新请求的接口返回来后,我们再调用resolve,逐个释放。

实现

这里我创建了一个localStorage.js文件,用于封装js的本地存储,我们要将从服务端获取的token存到浏览器缓存中,代码如下:

export default {
    //本地存储封装
    get(key){
        let data=localStorage.getItem(key);
        return JSON.parse(data)
    },
    set(key,val){
        let value=JSON.stringify(val);
        localStorage.setItem(key,value);
    }
}

创建了一个service.js文件,用于封装axios还有刷新token的实现,代码如下:

import axios from "axios"; //引入axios
import qs from "qs";//序列化post接口请求参数
import LocalStorage from "./localStorage.js"; //H5本地存储封装方法
let SECURITY_URL = "http://192.168.1.17:5505/security-amass/";//刷新token接口的base路径
axios.defaults.headers.post["Content-Type"] ="application/x-www-form-urlencoded;charset=UTF-8";
axios.defaults.headers.post["Access-Control-Allow-Origin"] = "*";
axios.defaults.withCredentials = false; // 携带cookie
// 创建一个axios实例
const instance = axios.create({
  timeout: 300000,
});
// 是否正在请求刷新token接口的标记
let isRefreshing = false;
// 请求队列
let requests = [];
//刷新token方法
function refreshToken(params) {
  return instance.get(SECURITY_URL + "oauth/token", params);
}
//请求结果拦截器
instance.interceptors.response.use(
  (response) => {
    // 接下来会在这里进行token过期的逻辑处理
    const config = response.config;
    let code = response.data.code;
    //这里的code值是跟后端约定好的, 40009代表token已经过期
    if (code == "40009") {
      if (!isRefreshing) {
        isRefreshing = true;
        let refresh_token = LocalStorage.get("refresh_token");
        //这里是我的项目中刷新token要传的参数,具体都传那些参数需要与后端开发确认
        let loginData = {
          grant_type: "refresh_token",
          client_id: "impawning",
          client_secret: "impawning",
          refresh_token,
        };
        refreshToken({ params: loginData })
          .then((res) => {
            let access_token = res.data.access_token;
            let refresh_token = res.data.refresh_token;
            LocalStorage.set("access_token", res.data.access_token);
            LocalStorage.set("refresh_token", res.data.refresh_token);
            config.headers["access_token"] = access_token;
            config.headers["refresh_token"] = refresh_token;
            requests.forEach((cb) => cb(access_token, refresh_token));
            requests = [];
            return instance(config);
          })
          .catch((err) => {
            window.location.href = "/";
          })
          .finally(() => {
            isRefreshing = false;
          });
      } else {
        // 正在刷新token,返回一个未执行resolve的promise
        return new Promise((resolve) => {
          // 将resolve放进队列,用一个函数形式来保存,等token刷新后直接执行
          requests.push((access_token, refresh_token) => {
            config.headers["access_token"] = access_token;
            config.headers["refresh_token"] = refresh_token;
            resolve(instance(config));
          });
        });
      }
    } else if (code == "40005" || code == "40003") {//这里代表token失效和token错误
      window.location.href = "/";
    } else {
      return response;
    }
  },
  (error) => {
    return Promise.reject(error);
  }
);

const api = {
  get(url, data) {
    instance.defaults.headers.common["access_token"] = LocalStorage.get(
      "access_token"
    );
    instance.defaults.headers.common["refresh_token"] = LocalStorage.get(
      "refresh_token"
    );
    return instance.get(url, { params: data });
  },
  post(url, data) {
    instance.defaults.headers.common["access_token"] = LocalStorage.get(
      "access_token"
    );
    instance.defaults.headers.common["refresh_token"] = LocalStorage.get(
      "refresh_token"
    );
    //post接口封装
    return instance.post(url, qs.stringify(data));
  },
};

export { api };

以上就是用axios实现无感知刷新token的示例,文章有疑问或错误的地方还请指出,感谢阅读。

你可能感兴趣的:(axios实现无感知刷新token)