Axios+Pinia实现前端无感刷新token

292 阅读1分钟

适用场景

  • 系统有一个较长时间(7天)的token
  • token过期时,直接返回到登录页面,
  • 如果token没有过期,达到了业务上定义的过期(距离token过期还有3天)

总结:有点类似于更新双token的长refresh_token

不适用场景

  • token过期时间较短(1小时-2小时)甚至更短;这个时候建议双token实现;一个短access_token,一个长refresh_token

实现方案

axios 请求拦截器

import { RefreshTokenUtil } from '@/refreshToken';

this.axiosInstance.interceptors.request.use(async (config) => {
  // 请求之前处理config
  const token = await RefreshTokenUtil.getToken(config.url);
  if (token) {
    config.headers.Authorization = token
  }
  return config;
}, undefined)

RefreshToken 核心类

import dayjs from 'dayjs'
import { useUserStoreWithOut } from '@/store/modules/user';
import { refreshTokenApi, REFRESH_TOKEN_URL } from '@/api/web/user'

class RefreshToken {
  constructor() {
    this.queue = [];
    this.refreshing = false;
  }
  async getToken(url) {
    const userStore = useUserStoreWithOut();
    if (url.endsWith(REFRESH_TOKEN_URL)) {
      return userStore.getToken
    }
    if (this.needRefresh()) {
      return this.enqueueRequest();
    }
    return userStore.getToken
  }
  needRefresh() {
    const userStore = useUserStoreWithOut();
    if (!userStore.getToken || !userStore.getExpiresAt) {
      return false
    }
    const expiresAt = dayjs(userStore.getExpiresAt);
    const now = dayjs();
    if (expiresAt.isBefore(now)) {
      return false
    }
    return expiresAt.isBefore(now.add(6, 'day'))
  }
  enqueueRequest() {
    return new Promise((resolve, reject) => {
      this.queue.push({ resolve, reject });
      if (!this.refreshing) {
        this.refreshToken();
      }
    });
  }
  async refreshToken() {
    try {
      this.refreshing = true
      const { expires_at, access_token } = await refreshTokenApi()
      const userStore = useUserStoreWithOut();
      userStore.setToken(access_token)
      userStore.setExpiresAt(expires_at)
      while (this.queue.length > 0) {
        const { resolve } = this.queue.shift();
        resolve(access_token)
      }
    } catch (error) {
      console.log(error)
    } finally{
      this.processQueue()
      this.refreshing  = false
    }
  }
  processQueue() {
    const userStore = useUserStoreWithOut();
    while (this.queue.length > 0) {
      const { resolve } = this.queue.shift();
      resolve(userStore.getToken)
    }
  }
}

export const RefreshTokenUtil = new RefreshToken();

pinia 的 user 模块

import { defineStore } from 'pinia'
import { store } from '@/store'

export const useUserStore = defineStore('user', {
  state: () => {
    return {
      token: null,
      expiresAt: ''
    }
  },
  getters: {
    getToken() {
      return this.token || ''
    },
    getExpiresAt() {
      return this.expiresAt
    }
  },
  actions: {
    setToken(token) {
      this.token = token
    },
    setExpiresAt(expiresAt = '') {
      this.expiresAt = expiresAt
    }
  },
  persist: true,
})

// Need to be used outside the setup
export function useUserStoreWithOut() {
  return useUserStore(store);
}

api 下的 user 模块

import { defHttp } from '@/utils/http/axios'

export const REFRESH_TOKEN_URL = 'refresh-token-api'

export const refreshTokenApi = () => {
  return defHttp.get({
    url: REFRESH_TOKEN_URL
  })
}

👏👏👏恭喜~ 到这里就结束了。