Token刷新机制
背景
现在的网站基本会设置授权访问,对于某些资源的访问,需要有权限才能访问或者操作,而服务端判断用户是否有权访问或者操作,一般是通过Token实现,而Token一般会设置过期时间,对于关于授权方面的技术这里不会具体描述,这篇主要针对的是用户访问授权资源的时候,Token过期了,如何实现无需再次登录,无痛刷新Token,重新请求。以下是几种可实现的方法
在前端实现刷新 token(access token)的方法有多种,每种方法都有其优点和缺点。以下是几种常见的方法及其评估:
1. 基于定时器的自动刷新
实现方式
使用
setTimeout
或
setInterval
定时在 token 过期前一定时间内自动发起刷新请求。
let refreshTokenTimeout;constsetRefreshTokenTimer=(expiresIn)=>{// 设置定时器,在 token 过期前 5 分钟刷新const refreshTime = expiresIn -300000;// 5分钟
refreshTokenTimeout =setTimeout(refreshToken, refreshTime);}constrefreshToken=async()=>{try{const response =awaitfetch('/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的时候同时存储当前的时间于localStoragesetRefreshTokenTimer(initialExpiresIn);
优点
- 简单易实现:逻辑简单,易于理解和实现。
- 自动化管理:无需用户干预,token 刷新完全自动化。
缺点
- 不适用于后台刷新:如果用户离开页面或浏览器 tab 被挂起,定时器可能无法正常工作。
- 资源消耗:可能会导致不必要的网络请求,特别是在用户不活跃时。
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 =awaitrefreshToken();
config.headers['Authorization']=`Bearer ${newTokenData.refreshToken}`;}else{
config.headers['Authorization']=`Bearer ${token}`;}return config;},(error)=>{return Promise.reject(error);});constrefreshToken=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);});
优点
- 高效:仅在需要时刷新 token,减少不必要的网络请求。
- 适用于后台刷新:即使页面在后台运行,也能确保 token 始终有效。
缺点
- 复杂度增加:需要处理并发刷新请求的问题,同一时刻多个请求可能触发多次刷新。 比如,
getApi1,getApi2同时并发请求,都会经过Token过期判断
,都会发情token刷新请求,对于页面并发请求,比如多文件上传之类的并发,如果并发6条数据,则会请求6次token的刷新。 - 额外延迟:首次请求可能因为 token 刷新而略有延迟。
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;};constaddPendingRequest=(callback:(token:string)=>void)=>{
pendingRequests.push(callback);};//是否正在刷新tokenlet 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')returnPromise.reject('登录失效,请重新登录');}if(!isRefreshing){try{
isRefreshing =true;//请求更新tokenconst data=awaitupdataToken(refreshToken);
localStorage.setItem('token', data.token);
localStorage.setItem('refreshToken', data.refreshToken);onTokenRefreshed(data.token);}catch(err){
window.location.href('/login')returnPromise.reject(err)}finally{
isRefreshing =false}}//存储当前请求returnnewPromise<AxiosResponse<any>>((resolve)=>{addPendingRequest((newToken:string)=>{
response.config.headers['Authorization']='Bearer'+ newToken;resolve(http(response.config));});});}returnPromise.reject(error);});
优点
- 高效:只有在必要时才刷新 token,避免不必要的请求。
- 灵活性:能够处理 token 失效后的各种情况,包括并发问题。
缺点
- 复杂度较高:需要处理并发刷新、请求队列等问题,代码复杂度高。
- 延迟:第一次请求失败后需要等待 token 刷新,再次重试,可能会增加延迟。
总结
方法优点缺点基于定时器的自动刷新简单易实现,自动化管理不适用于后台刷新,可能导致不必要的网络请求基于请求拦截器的刷新高效,适用于后台刷新,减少不必要的网络请求,复杂度增加,需要处理并发刷新问题,同一时刻多个请求可能触发多次刷新。首次请求可能延迟基于响应拦截器的刷新只有在必要时才刷新 token,避免不必要的请求,能够处理 token 失效后的各种情况,包括并发问题。首次请求失败可能增加延迟
选择哪种方法取决于你的具体需求和系统架构,通常基于请求拦截器和响应拦截器的方法更为普遍,因为它们能够更好地应对复杂的实际场景。
版权归原作者 Backstroke fish 所有, 如有侵权,请联系我们删除。