提示

  在正式的开发中,Token 处理往往伴随着 路由导航守卫 的使用。

# 一、封装工具

# 1、cookies 方式

  • utils / cache.js
import Cookies from 'js-cookie'

const TokenKey = 'lencamo'

export function getToken() {
  return Cookies.get(TokenKey)
}

export function setToken(token) {
  return Cookies.set(TokenKey, token)
}

export function removeToken() {
  return Cookies.remove(TokenKey)
}

# 2、localStorage 方式

提示

  关闭浏览器 sessionStroage 会被清除,但 localStroage 不会

  • utils / cache.js
const localStorage = window.localStorage

export default {
  set(key, value) {
    localStorage.setItem(key, JSON.stringify(value))
  },

  get(key) {
    return JSON.parse(localStorage.getItem(key)) || null
  },

  remove(key) {
    localStorage.removeItem(key)
  },

  clear() {
    localStorage.clear()
  }
}

# 3、storage 综合封装

  我们可以使用 class 类 和 enum 枚举实现综合封装。

enum CacheType {
  Local,
  Session
}

class Cache {
  storage: Storage

  constructor(storageType: CacheType) {
    this.storage = storageType === CacheType.Local ? localStorage : sessionStorage
  }

  setCache(key: string, value: any) {
    this.storage.setItem(key, JSON.stringify(value))
  }

  getCache(key: string) {
    const value = this.storage.getItem(key)
    return value ? JSON.parse(value) : null
  }

  removeCache(key: string) {
    this.storage.removeItem(key)
  }

  clearCache() {
    this.storage.clear()
  }
}

const localCache = new Cache(CacheType.Local)
const sessionCache = new Cache(CacheType.Session)

export { localCache, sessionCache }

# 二、使用场景

  通常情况下 token 的使用有下面两步:

# 1、持久化存储

  • cookies 方式
import { getToken, setToken, removeToken } from '@/utils/auth'

const getDefaultState = () => {
  return {
    token: getToken() // vuex存储的是cookie中的token
  }
}

const state = getDefaultState()

const mutations = {
  // 1、token处理
  SET_TOKEN: (state, token) => {
    state.token = token
    // 持久化存储token
    setToken(token)
  },
  REMOVE_TOKEN: (state, token) => {
    state.token = ''
    removeToken()
  }
}
  • localStorage
import storage from '@/common/storage'

const state = {
  userInfo: storage.get('userInfo'),
  token: localStorage.getItem('TOKEN')
}

# 2、token 挂载

  • axios 实例上
const RenRequest = axios.create({
  baseURL: '',
  headers: {
    Authorization: `Bearer ${getToken}`
  }
})
  • axios 拦截器上
request.interceptors.request.use((config) => {
  const token = store.getters.token
  if (token) {
    config.headers['X-token'] = `Bearer ${token}`
  }
  return config
})

# 三、无感 token 刷新

  常用于 OAuth2.0 授权登录中,因为这种模式下通常会有两个 token:access_tokenrefresh_token

oAuth2.0 中 access_token 默认有效时长为 12 个小时,refresh_token 默认时长为 30 天。在实际运用中需要根据需求设置有效时长。

# 1、refresh 接口

let promise // 节流
export async function refreshTokenApi() {
  if (promise) {
    return promise
  }

  promise = new Promise(async (resolve) => {
    const response = await request.get('/refresh_token', {
      header: {
        Authorization: `Bearer ${getRefreshToken()}`
      },
      __isRefreshTokenAPI: true
    })

    return response.code === 0
  })

  promise.finally(() => {
    promise = null
  })
}

export function isRefreshTokenAPI(config) {
  return !!config.__isRefreshTokenAPI
}

# 2、逻辑处理

const RenRequest = axios.create({
  baseURL: '',
  headers: {
    Authorization: `Bearer ${getToken()}`
  }
})

// 响应式拦截器
request.interceptors.response.use(
  async (response) => {
    // 1、存储token、refresh_token
    if (response.headers.authorization) {
      const token = response.headers.authorization.replace('Bearer', '')
      setToken(token)
      RenRequest.default.headers.Authorization = `Bearer ${token}`
    }
    if (response.headers.refresh_token) {
      const refresh_token = response.headers.refresh_token.replace('Bearer', '')
      setRefreshToken(refresh_token)
    }

    // 2、token过期处理
    // 前提:出现无权限401、排除refreshTokenApi请求
    if (response.headers.code === 401 && !isRefreshTokenAPI(response.config)) {
      const isSuccess = await refreshTokenApi() // 获取新的token

      // refresh_token是否过期
      if (isSuccess) {
        RenRequest.default.headers.Authorization = `Bearer ${getToken()}` // 使用新的token

        const res = await RenRequest.request(res.config)
        return resp
      } else {
        // 跳转到登录页
      }
    }

    return response.data
  },
  (error) => {
    return error
  }
)
更新于 : 8/7/2024, 2:16:31 PM