前端无痛刷新Token

本文介绍了前端实现无痛刷新Token的三种方案,并重点分析了最佳实践——在请求响应拦截器中自动刷新Token的实现方式,包括创建请求队列、设置刷新标志及详细流程。通过这种方式,可以避免用户在提交复杂表单时因Token过期导致的不佳体验。

前言

记得上年大三期间,受同学邀请一起重写学校的一个实验系统,当时真的是感觉啥也不会(现在会的也不多,哈哈)。在我们几个进行讨论的时候就说了这个需求。这个系统主要功能是学生们进行提交一些表单操作,所以这个需求挺关键的。但当时因自身技术不行,就略过了这个功能。今年也是刚步入实习阶段,在阅读公司以前项目的时候就发现了无感刷新Token的封装痕迹,但只是一些雏形,就是函数封装了一半,不知道什么原因最后也没有使用。
决定使用自己仅有的技术重新梳理一下
无痛刷新Token的需求场景很多,例如:用户正在提交一个复杂的表单,突然拉肚子取了个厕所,回来点击提交表单发现Token失效,强制返回了登录页。我要是当时的用户,我可能会pen死开发的,哈哈,这种用户体验感很差。所以说前端实现无痛刷新Token是非常有必要的。


一、方案

  • 方案一
    后端返回过期时间,前端每次请求就判断token的过期时间,如果快到过期时间,就去调用刷新token接口。
    缺点:需要后端额外提供一个token过期时间的字段;使用了本地时间判断,若本地时间被篡改,特别是本地时间比服务器时间慢时,拦截会失败。
  • 方案二
    写个定时器,定时刷新token接口。
    缺点:浪费资源,消耗性能,不建议采用。
  • 方案三
    在请求响应拦截器中拦截,判断token 返回过期后,调用刷新token接口。

综合上面的三个方法,最好的是第三个,因为它不需要占用额外的资源。
接下来,我们就分析一下第三种的实现方式

二、分析

1.创建一个数组队列pendingQueue存放需要重新发起的请求
2. 创建一个flag ifRefreshPending来判断是否刷新中
3. 在请求拦截器中将请求添加到pendingQueue
4. 在响应拦截器中判断Token是否过期:
    1.Token没有过期,将pendingQueue中的本次请求移除,正常返回响应数据
    2.Token过期,刷新TokenifRefreshPending判断是否正在刷新中,防止重复刷新TokenToken刷新
成功,将pendingQueue存放的请求重新请求一次,然后清空pendingQueue

三、简易封装

// 存储pending状态下的请求
const pendingQueue = new Map()
// 是否正在刷新的标志
let ifRefreshPending = false
// 刷新Token的接口函数
const refreshToken = () => {
    return new Promise((resolve, reject) => {
        // 通过接口获取新的token
		// ...	
        resolve(token)
    })
}
// 添加请求
const addPending = config => {
    const {url, method, data} = config
    if(!pendingQueue.has(url)){
        // 将请求添加到task
        pendingQueue.set(url,{
            url,
            method,
            data
        })
    }
}
// 移除请求
const removePending = config =>{
    pendingQueue.delete(config.url)
}
axios.interceptors.request.use(config =>{
	removePending(config)  // 再添加之前移除之前的本次请求
    addPending(config)
    // 添加Token
    config.headers.Authorization = localStorage.getItem('token')
    return config
})
  • 假设和后端约定Token过期返回状态码 code = 0
axios.interceptors.response.use(response =>{
    if(response.code === 0){
        // 说明整在刷新Token
        ifRefreshPending = true
        if(!ifRefreshPending){
            // TODO: 刷新Token, 将pendingTask中存储的请求重新执行
            return refreshToken().then(res => {
                localStorage.setItem('token', res.token)
                // 将pendingQueue中存储的请求重新执行
                pendingQueue.forEach(item => {
					// TODO 重新发起请求	
				})
				// 清空pendingQueue
				pendingQueue.clear()
            }).catch(err=>{
                // 刷新Token失败就跳到登录页
                router.push('/login')
                return Promise.reject(err)
            }).finally(() => {
                ifRefreshPending = false
            })
        }
    }
    // 正常响应就移除本次请求
    removePending(response.config)
    return response && response.data
})

总结

技术不精,谅!!!

前端实现无感刷新 Token 的机制,主要目的是在用户无感知的情况下,自动完成 Token 的更新,从而避免因 Token 过期而中断用户的操作,同时提升系统安全性与用户体验。以下是实现无感刷新 Token 的主要方法: ### 请求拦截与响应拦截机制 前端通常通过封装 HTTP 请求库(如 Axios 或 Fetch)来实现请求和响应的拦截。在请求发出前,自动注入当前有效的 Token;在接收到响应后,若发现 Token 失效(如返回 401 未授权状态),则触发刷新 Token 的流程[^2]。 ```javascript // 示例:使用 Axios 实现请求与响应拦截 const apiClient = axios.create({ baseURL: 'https://api.example.com', }); let isRefreshing = false; let failedQueue = []; apiClient.interceptors.request.use(config => { const token = localStorage.getItem('token'); if (token) { config.headers['Authorization'] = `Bearer ${token}`; } return config; }); apiClient.interceptors.response.use( response => response, async error => { const originalRequest = error.config; if (error.response.status === 401 && !originalRequest._retry) { if (isRefreshing) { return new Promise((resolve, reject) => { failedQueue.push({ resolve, reject }); }).then(token => { originalRequest.headers['Authorization'] = `Bearer ${token}`; return axios(originalRequest); }).catch(err => Promise.reject(err)); } originalRequest._retry = true; isRefreshing = true; try { const refreshToken = localStorage.getItem('refreshToken'); const response = await axios.post('/auth/refresh-token', { refreshToken }); const { token } = response.data; localStorage.setItem('token', token); processQueue(null, token); originalRequest.headers['Authorization'] = `Bearer ${token}`; return axios(originalRequest); } catch (err) { processQueue(err, null); return Promise.reject(err); } finally { isRefreshing = false; } } return Promise.reject(error); } ); function processQueue(error, token) { failedQueue.forEach(prom => { if (error) { prom.reject(error); } else { prom.resolve(token); } }); failedQueue = []; } ``` ### 利用 Refresh Token 机制 为了实现无感刷新,通常采用双 Token 机制:`Access Token` 和 `Refresh Token`。Access Token 用于日常请求,生命周期较短;Refresh Token 用于获取新的 Access Token,生命周期较长但应受到频率限制以增强安全性[^3]。 - **Access Token**:用于每次请求的鉴权,有效期较短(如 15 分钟)。 - **Refresh Token**:用于在 Access Token 过期时获取新的 Access Token,有效期较长,但应设置使用频率限制(如每小时最多使用一次)。 ### 安全性增强措施 - **非对称加密签名**:使用 RSA 等非对称加密算法对 JWT 签名,防止 Token 被篡改[^3]。 - **限制 Refresh Token 使用频率**:避免被恶意高频使用,例如限制为每小时最多刷新一次。 - **存储安全**:将 Token 存储于 `HttpOnly` 的 Cookie 中,或使用 `localStorage` 时结合加密手段。 ### 批量请求处理机制 在多个请求同时遇到 Token 失效的情况下,应避免多次调用刷新 Token 接口。可以通过维护一个“失败请求队列”,在刷新 Token 成功后统一重试这些请求,从而减少服务器压力并提升用户体验[^3]。 ---
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值