不少管理系统都使用了token机制,虽然token有不少优点。但缺点也明显,例如: 如果token我们后台签发了,但token给盗用了,常规情况下我们没办法让token主动过期,如果要想让token主动过期,我们得配合redis中间件。

token如果设置过期时间太短,安全性提高了,但用户体验下降了,你也不想在入录表单的时候数据填好了,一保存就给我跳转到登入页面吧,这种情况相当炸裂,之前的操作都要重新来一遍了。但如果token设置时间太长,系统安全又大大降低。那有没有一种更好的方式来提高t系统的安全性和使用体验呢。

token刷新极其过期请求重新发送,具体实现如下:

新建refresh-token.ts文件,代码如下:

import { AxiosResponse } from 'axios'
import { refreshToken } from '@/api/user/admin-login'
import router from '@/router'

import { setToken, clearToken } from '@/utils/common/auth'

let updateTokenTag = true,
  requestArr: any[] = []

/**
 * 刷新token
 * @param response
 * @param axios
 * @returns
 */
export const refreshTokenHandle = async (
  response: AxiosResponse,
  axios: any
) => {
  const config = response.config
  if (updateTokenTag) {
    console.log('正在刷新短token')
    updateTokenTag = false
    const config = response.config
    //获取刷新token
    const token = localStorage.getItem('refreshToken') as string
    try {
      //刷新token
      const res = await refreshToken(token)
      if (res.code === 200) {
        //设置token
        setToken(res.data.accessToken, res.data.refreshToken)
        // 执行完更新操作,重新执行未执行请求
        requestArr.forEach((execute) => execute())
        // 置空队列数组
        requestArr = []
        config.headers['satoken'] = res.data.accessToken

        console.log('token刷新成功')

        return axios(config)
      } else {
        console.log('token刷新失败')
        requestArr = []
        clearToken()
        router.push('/cvmagic/login')
      }
      // 这里是第一次执行进入更新token的接口继续往下执行
    } catch {
      clearToken()
      router.push('/cvmagic/login')
    } finally {
      updateTokenTag = true
    }
  } else {
    // 通过异步将并行的接口加入队列中,等上面更新完token接口再执行队列
    return new Promise((resolve) => {
      // 用函数的方式将相应数据resolve出去,执行函数就能得到响应结果
      requestArr.push(() => {
        return resolve(axios(config))
      })
    })
  }
  return response
}

这有俩个难点

一.是如何防止token刷新接口重复执行。

解决:我们设置updateTokenTag开关判断当前是否进入token刷新状态,进入后把状态设置为false,当token刷新结束设置会true

二.如何让token过期的请求重新执行。

解决:定义一些数组,把其他的请求放入数组中,等待token刷新完毕后再重新执行。

接下来我们就前端双token实现。axios二次封封装,导入refresh-token.ts在axios响应拦截器处理刷新token

import axios from 'axios'
import type { AxiosInstance } from 'axios'
import * as message from './message'
import { codeEnum } from '@/enums/codeEnum'
import { refreshToken } from '@/api/user/admin-login'
import router from '@/router'

import { refreshTokenHandle } from './refresh-token'

const http: AxiosInstance = axios.create({
  baseURL: import.meta.env.VITE_API_BASE_URL,
  timeout: 5000,
})

//重试次数
const count = 5
//重试计数
let num = 1

// 请求前拦截
http.interceptors.request.use(
  (config) => {
    // const token = JSON.parse(localStorage.getItem('userinfo')).token
    config.headers['satoken'] = localStorage.getItem('satoken')
    return config
  },
  (err) => {
    console.log(err)
  }
)

// 请求数据返回前拦截
http.interceptors.response.use(async (response) => {
  console.log(response.data, response.config.url)
  message.msg(response.data.code, response.data.msg)

  if (response.data.code === 401) {
    return refreshTokenHandle(response, axios)
  }

  return response
})

export const get = <T = unknown>(...args: any): Promise<T> => {
  const [url, params] = args
  return new Promise((resolve, reject) => {
    http
      .get(url, { params })
      .then((res) => {
        resolve(res.data)
      })
      .catch(() => {
        if (num <= count) {
          console.log(`重试次数:${num}`)
          num++
          get(url, params)
        } else {
          reject(url + 'get请求失败')
          return
        }
      })
  })
}

export const post = <T = unknown>(...args: any): Promise<T> => {
  const [url, params, headers] = args

  return new Promise((resolve, reject) => {
    http
      .post(url, params, headers)
      .then((res) => {
        resolve(res.data)
      })
      .catch((err) => {
        reject({
          err,
          msg: 'post请求失败',
        })
      })
  })
}

export const del = <T = unknown>(...args: any): Promise<T> => {
  const [url, data, headers] = args
  return new Promise((resolve, reject) => {
    console.log(url)

    http
      .delete(url, {
        data,
      })
      .then((res) => {
        resolve(res.data)
      })
      .catch((err) => {
        reject({
          err,
          msg: 'delete请求失败',
        })
      })
  })
}

export const put = <T = unknown>(...args: any): Promise<T> => {
  const [url, params, headers] = args

  return new Promise((resolve, reject) => {
    console.log(url)
    http
      .put(url, params, headers)
      .then((res) => {
        resolve(res.data)
      })
      .catch((err) => {
        reject({
          err,
          msg: 'put请求失败',
        })
      })
  })
}

Logo

华为开发者空间,是为全球开发者打造的专属开发空间,汇聚了华为优质开发资源及工具,致力于让每一位开发者拥有一台云主机,基于华为根生态开发、创新。

更多推荐