如何实现token无感刷新

232 阅读1分钟

设计思想:

1.login接口返回两个token,token和refresh token。将两个token缓存在本地

2.当接口返回401时,token过期,需要拿refresh token去换取新的token。将新的token替换旧的token。

3.多个接口返回401时,refresh token接口只需要发送一次,避免多次发送refresh token的请求。

4.将页面请求接口保存起来,当refresh token接口请求成功后,重新发送页面请求。

5.当refresh token也过期时,重新登录。

import axios from "axios";

const service = axios.create({
  baseURL: "https://api.example.com",
});
const requestsQueue = [];
// 请求拦截器
service.interceptors.request.use(
  (config) => {
    const token = localStorage.getItem("token");
    if (token) {
      config.headers["Authorization"] = `Bearer ${token}`;
    }
    return config;
  },
  (error) => {
    return Promise.reject(error);
  }
);

// 响应拦截器
service.interceptors.response.use(
  (response) => {
    return response;
  },
  (error) => {
    let { config, data } = error.response;
    // 判断Token是否过期
    if (data && data.status === 401 && !isRefreshRequest(config)) {
      //如果是refreshToken也失效了,就不发送刷新token的接口。
      //保存页面请求接口
      requestsQueue.push(config);
      // Token过期,尝试刷新Token
      return refreshToken()
        .then((newToken) => {
          // 存储新的Token
          localStorage.setItem("token", newToken);
          // 使用新的Token重新发送之前的请求
          config.headers["Authorization"] = `Bearer ${newToken}`;
          retryRequestQueue(newToken);
          return service(config);
        })
        .catch(() => {
          alert("登录过期,请重新登录");
        });
    }
    return Promise.reject(error);
  }
);

let refreshTokenPromise = null;
function refreshToken() {
  if (refreshTokenPromise) {
    // 如果已经有一个刷新请求正在进行,直接返回该Promise
    return refreshTokenPromise;
  }
  refreshTokenPromise = new Promise((resolve, reject) => {
    axios
      .get("/auth/refresh", {
        headers: {
          Authorization: `Bearer ${localStorage.getItem("refreshToken")}`,
        },
        __isRefreshToken: true,
      })
      .then((response) => {
        // 存储新的Token
        localStorage.setItem("token", response.data.accessToken);
        localStorage.setItem("refreshToken", response.data.refreshToken);
        resolve(response.data.accessToken);
      })
      .catch((error) => {
        reject(error);
      });
  });
  refreshTokenPromise.finally(() => {
    refreshTokenPromise = null;
  });
  return refreshTokenPromise;
}

function isRefreshRequest(config) {
  return !!config.__isRefreshToken;
}

function retryRequestQueue(newToken) {
  requestsQueue.forEach((config) => {
    config.headers["Authorization"] = `Bearer ${newToken}`;
    axios(config);
  });
  requestsQueue.length = 0; // 清空队列
}