javascript技巧

关注公众号 jb51net

关闭
首页 > 网络编程 > JavaScript > javascript技巧 > 前端实现Token刷新

前端实现Token刷新机制的几种方法

作者:Backstroke fish

本文探讨Token过期时的无痛刷新方案,包括定时器、请求拦截器、响应拦截器,建议优先采用后两者以兼顾效率与场景适配性,感兴趣的可以了解一下

背景

现在的网站基本会设置授权访问,对于某些资源的访问,需要有权限才能访问或者操作,而服务端判断用户是否有权访问或者操作,一般是通过Token实现,而Token一般会设置过期时间,对于关于授权方面的技术这里不会具体描述,这篇主要针对的是用户访问授权资源的时候,Token过期了,如何实现无需再次登录,无痛刷新Token,重新请求。以下是几种可实现的方法

在前端实现刷新 token(access token)的方法有多种,每种方法都有其优点和缺点。以下是几种常见的方法及其评估:

1. 基于定时器的自动刷新

实现方式

使用 setTimeoutsetInterval 定时在 token 过期前一定时间内自动发起刷新请求。

let refreshTokenTimeout;

const setRefreshTokenTimer = (expiresIn) => {
  // 设置定时器,在 token 过期前 5 分钟刷新
  const refreshTime = expiresIn - 300000; // 5分钟
  refreshTokenTimeout = setTimeout(refreshToken, refreshTime);
}

const refreshToken = async () => {
  try {
    const response = await fetch('/refresh-token');
    const data = await response.json();
    // 假设返回数据包含新的 access token 和它的过期时间(expiresIn)
    localStorage.setItem('refreshToken', data.refreshToken);
    setRefreshTokenTimer(data.expiresIn);
  } catch (error) {
    console.error("Failed to refresh token", error);
    // 可以根据需要处理错误,例如跳转到登录页面
  }
}

// 初次设置定时器,这种需要后台返回token的过期时间,或者用户登录的时候获取token的时候同时存储当前的时间于localStorage
setRefreshTokenTimer(initialExpiresIn);

优点

缺点

2. 基于请求拦截器的刷新

实现方式

在每个 API 请求之前检查 token 是否即将过期,如果即将过期,则首先刷新 token,然后再继续发送请求。

// 使用 Axios 作为示例请求库
import axios from 'axios';

const http= axios.create({
  baseURL: '/api',
  timeout: 10000,
});

http.interceptors.request.use(async (config) => {
  const token = localStorage.getItem('refreshToken');
  const expiryTime = localStorage.getItem('tokenExpiryTime');

  if (expiryTime && Date.now() >= expiryTime - 300000) { // 提前5分钟刷新
    const newTokenData = await refreshToken();
    config.headers['Authorization'] = `Bearer ${newTokenData.refreshToken}`;
  } else {
    config.headers['Authorization'] = `Bearer ${token}`;
  }

  return config;
}, (error) => {
  return Promise.reject(error);
});

const refreshToken = async () => {
  try {
    const response = await apiClient.post('/refresh-token');
    const data = await response.data;
    localStorage.setItem('refreshToken', data.refreshToken);
    localStorage.setItem('tokenExpiryTime', Date.now() + data.expiresIn);
    return data;
  } catch (error) {
    console.error("Failed to refresh token", error);
    throw error;
  }
};

// 在需要时使用 apiClient 发出请求
http.get('/some-protected-endpoint').then(response => {
  console.log(response.data);
});

优点

缺点

3. 基于响应拦截器的刷新(最常用)

实现方式

当 API 请求返回 401 Unauthorized 错误时,尝试刷新 token 并重新发送原始请求。

import axios, { AxiosInstance, AxiosRequestConfig, AxiosResponse, InternalAxiosRequestConfig } from 'axios';
const http: AxiosInstance = axios.create({
  baseURL: '/baseapi', //Mock
  timeout: 10000,
});
const openTokenRefresh = false; // 是否开启token刷新,如果开启,就设置为true就好
const pendingRequests: ((token: string) => void)[] = []; // 保存所有请求的回调,用于刷新token后重新请求
const onTokenRefreshed = (token: string):void => {
  // 刷新token后,重新请求,
  pendingRequests.forEach((callback) => {
    callback(token);
  });
  pendingRequests.length = 0;
};
const addPendingRequest = (callback: (token: string) => void) => {
  pendingRequests.push(callback);
};
//是否正在刷新token
let isRefreshing = false;

http.interceptors.response.use((response: AxiosResponse<any>) => response, async (error) => {
  if (error.response && error.response.status === 401 && openTokenRefresh) {
  	//获取refreshToken 
 	const refreshToken = localStorage.getItem('refreshToken');
    if (!refreshToken) {
        //没有refreshToken,跳转登录页面
      window.location.href('/login')
      return Promise.reject('登录失效,请重新登录');
    }
    if (!isRefreshing) {
      try{
         isRefreshing = true;
         //请求更新token
      	 const data= await updataToken(refreshToken);
       	 localStorage.setItem('token', data.token);
       	 localStorage.setItem('refreshToken', data.refreshToken);
       	 onTokenRefreshed(data.token);
       }catch(err){
       	  window.location.href('/login')
       	  return Promise.reject(err)
       }
       finally{
      	  isRefreshing = false
       }
    }
	      //存储当前请求
      return new Promise<AxiosResponse<any>>((resolve) => {
        addPendingRequest((newToken: string) => {
          response.config.headers['Authorization'] ='Bearer'+ newToken;
          resolve(http(response.config));
        });
      });
  }

  return Promise.reject(error);
});

优点

缺点

总结

方法优点缺点
基于定时器的自动刷新简单易实现,自动化管理不适用于后台刷新,可能导致不必要的网络请求
基于请求拦截器的刷新高效,适用于后台刷新,减少不必要的网络请求,复杂度增加,需要处理并发刷新问题,同一时刻多个请求可能触发多次刷新。首次请求可能延迟
基于响应拦截器的刷新只有在必要时才刷新 token,避免不必要的请求,能够处理 token 失效后的各种情况,包括并发问题。首次请求失败可能增加延迟

选择哪种方法取决于你的具体需求和系统架构,通常基于请求拦截器和响应拦截器的方法更为普遍,因为它们能够更好地应对复杂的实际场景。

到此这篇关于前端实现Token刷新机制的几种方法的文章就介绍到这了,更多相关前端实现Token刷新内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

您可能感兴趣的文章:
阅读全文