Token刷新机制

Backstroke fish 2024-08-30 15:03:39 阅读 97

Token刷新机制

背景1. 基于定时器的自动刷新实现方式优点缺点

2. 基于请求拦截器的刷新实现方式优点缺点

3. 基于响应拦截器的刷新(最常用)实现方式优点缺点

总结

背景

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

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

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

实现方式

使用 <code>setTimeout 或 setInterval 定时在 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);

优点

简单易实现:逻辑简单,易于理解和实现。自动化管理:无需用户干预,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 = 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);

});

优点

高效:仅在需要时刷新 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;

};

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,避免不必要的请求,能够处理 token 失效后的各种情况,包括并发问题。 首次请求失败可能增加延迟

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



声明

本文内容仅代表作者观点,或转载于其他网站,本站不以此文作为商业用途
如有涉及侵权,请联系本站进行删除
转载本站原创文章,请注明来源及作者。