token无感刷新实现步骤

727 阅读3分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第3天,点击查看活动详情

前言

上片文章写到了 axios基础版封装 ,已经基本可以满足日常的开发需求了,但是!这就够了吗?登录功能在系统中最常见不过了,此时产品走了过来说道,咱们要加一个需求,目前咱们的token时间有些短,token过期就要重新去登录,用户此时需要频繁的去登录,这种体验非常不好。 我心想直接把token时间设置的长一点就行了,甩给后端就行,但是经过后端同学的上课后才知道我的想法low到极致。

经过了解后端设置时间太长会存在安全问题,避免安全问题所以还是需要前端配合完成

实现思路

需要两个token存储(过期)时间不同。短token用来请求应用数据,长token用于获取新的短token。后端提供一个获取短token的接口,我们在axios响应拦截的时候去判断短token(应用数据)有没有过期,若过期利用长token去请求接口拿到新的短token,进行重新赋值,实现token无感的刷新,若长token过期则需要用户重新登录(安全烤考虑)。

const ACCESS_TOKEN  = "s_tk"  
const REFRESH_TOKEN  = "l_tk"

需要和后端约定token过期的code值,方便判断和排查

//code.js
// 需要重新登陆
export const CODE_RELOGIN = 410;
// 应用数据token过期
export const CODE_TOKEN_EXPIRED = 411;
//接口请求成功
export const CODE_SUCCESS = 0;

定义一些关于token的常量存储

//token-info.js  常量
export const ACCESS_TOKEN  = "s_token"      //短token
export const REFRESH_TOKEN = "l_token"; //长token

export const AUTH = "Authorization"; 
export const REFRESH = "REFRESH"; 

实现

依旧去创建axios实例,在请求拦截和请求响应中分别去做一些事情。

import axios from "axios";
import { ACCESS_TOKEN, AUTH } from "./token-info";
import { CODE_RELOGIN, CODE_TOKEN_EXPIRED, CODE_SUCCESS } from "./code"
import { refreshAccessToken, NoneTokenRequestList } from "./refresh"

const service = axios.create({
  baseURL:'',
  timeout:10000,
  withCredentials:false, //跨域请求是否要携带cookie
})
// 请求拦截
service.interceptors.request.use(
    (config) => {
      const s_tk = localStorage.getItem(ACCESS_TOKEN);
      s_tk && config.headers[AUTH]
      return config;
    },
    (error) => {
      return Promise.reject(error);
    }
);

// 响应拦截
service.interceptors.response.use(
  response => {
    let { config, data} = response
    const code = Number(res.data.code) || 200
    
      return new Promise((resolve, reject) => {
        if(code === CODE_TOKEN_EXPIRED) {  //说明token过期了
          // ...做点事情吧,为了不侵入业务代码,抽取两个函数进行处理
          NoneTokenRequestList(() => resolve(service(config)))
          refreshAccessToken()
        }else {
          return reject(data)
        }
      })
    
  },
  (error) => {
    return Promise.reject(error)
  }
)
export default service

在services文件下新建一个refresh.js文件用来处理token逻辑,这里我们考虑两个问题

  • 防止多次刷新token

第一个请求过期去请求刷新token的接口,此时又有一个接口刚好过期,那么也要去刷新token,获取新token的接口被重复请求了两次,这已经造成了性能浪费

  • 同时两个以上的请求时,其他接口如何重试

当第一个请求过期去刷新token的时候,我们希望后面的接口能够在等待ing,可以利用队列去把后面的请求存进去,队列先进先出,利用promise让队列中的排队的请求处于等待中

具体代码如下:

import service from "./http";
import { ACCESS_TOKEN, REFRESH_TOKEN, REFRESH } from "./token-info";
let subscribers = []
let isRefreshing = false
export const NoneTokenRequestList = (request) => {
  subscribers.push(request)
}

export const retryRequest = () => {
  subscribers.forEach((request) => request())
  subscribers = []
}

export const refreshAccessToken = async () => {
  if(!isRefreshing) {
    try {
      isRefreshing = true
      const l_token = localStorage.getItem(REFRESH_TOKEN);
      if (l_token) {
        /* 利用长token重新获取短token */
        const { accessToken } = await service.get(
          "/请求新的token的接口",
          { headers: { [REFRESH]: l_token } }
        );
        localStorage.setItem(ACCESS_TOKEN, accessToken);
        retryRequest()
      }
      return;
    } catch (err) {
      clearAuthAndRedirect()
      return
    } finally {
      isRefreshing = false
    }
  }
}

/* 清除长短token,并定位到登录页(在项目中使用路由跳转) */
export const clearAuthAndRedirect = () =>{
  localStorage.removeItem(ACCESS_TOKEN)
  window.location.href = '/login'
}

感谢大佬的文章指引:

实现无感刷新token我是这样做的

封装 axios 拦截器实现用户无感刷新 access_token

总结

作为一个刚刚毕业的前端小白,希望在这里可以记录自己的点滴成长,开拓眼界,养成习惯!

微信图片_20220822200003.jpg