前端 axios 如何实现一个简单的无感知登录

142 阅读1分钟

我们知道如果没有权限,那么接口返回的状态值是401,请求接口的时候,接口鉴权需要我们在请求头携带token值,而无感知登录的话,一般有access_token和refresh_token,在请求刷新的时候,一般接口会返回access_token和refresh_token值,那么什么情况下才去请求refresh接口呢?这么逻辑状态值就是access_token 401 并且是非刷新接口/refrsh, ok 根据这个条件我们可以写出这样的代码

if(response.status === '401' && !config.url.includes("/refresh")) {
   1:请求refresh 接口,重新把access_token和refresh_token的值存到localStoreage去
   2: 重写更改config下的请求头的headers下的AutherToken值,最后return axios(config),从而实现无感登录
}

具体代码

import axios, { AxiosRequestConfig, AxiosResponse } from "axios";
interface PendingTask {
  config: AxiosRequestConfig;
  resolve: (value: AxiosResponse | PromiseLike<AxiosResponse>) => void;
  reject: (reason?: any) => void;
}
const axioxInstance = axios.create({
  baseURL: "http://localhost:3000/api",
  timeout: 10000,
  headers: {
    "Content-Type": "application/json",
  },
});
const pendingTasks: PendingTask[] = []; // 存储请求队列
let isRefreshing = false; // 更清晰的命名

// 请求拦截器
axioxInstance.interceptors.request.use((config) => {
  // 在这里添加请求拦截器
  let tokenKey = localStorage.getItem("access_token") as string;
  const token = localStorage.getItem(tokenKey) as string;
  if (token) {
    config.headers.Authorization = `Bearers ${token}`;
  }
  return config;
});
// 返回响应拦截器
axioxInstance.interceptors.response.use(
  (res) => {
    return res.data;
  },
  async (error) => {
    const { config, response } = error;
    if (response.status === "401" && !config.url.includes("/refreshToken")) {
      // 是否是已经在无刷新token了
      if (isRefreshing) {
        // 如果是,返回一个新的promise,并且把他推到任务队列去
        return new Promise((resolve, reject) => {
          pendingTasks.push({
            config,
            resolve,
            reject,
          });
        });
      }
      isRefreshing = true; // 设置为正在刷新状态
      const res = await refreshToken();
      if (res.status === 200) {
        // 释放所有等待的请求
        pendingTasks.forEach((task) => task.resolve(task.config));
        // 清空队列
        pendingTasks.length = 0;
        config.headers.Authorization = `Bearer ${res.data.access_token}`; // 更新请求头的token值
        return axioxInstance(config); // 返回这样的一个config,就是为了实现一个无感的请求,此时所有的请求中的config都已经拿到最新的token值了,就可以正常请求了
      }
    } else {
      // 失败的时候,也要把队列清空,并且要把错误信息返回出去
      pendingTasks.forEach((task) => task.reject(error));
      pendingTasks.length = 0;
      alert("登录过期,请重新登录");
      return error.response;
    }
  }
);

async function refreshToken() {
  const res = await axioxInstance.get("/refresh", {
    params: {
      token: localStorage.getItem("refresh_token"),
    },
  });
  localStorage.setItem("access_token", res.data.access_token);
  localStorage.setItem("refresh_token", res.data.refresh_token);
  return res;
}