likes
comments
collection
share

前端面试:前端如何无感刷新token;token还没有刷新完成时,发送了其他请求需要如何处理

作者站长头像
站长
· 阅读数 4

在前端开发中,实现无感刷新 token 通常是为了保持用户的登录状态,并确保其访问权限的有效性。下面将介绍如何实现无感刷新 token 的原理以及给出一个示例。

前端如何无感刷新token 实现原理

无感刷新 token 的实现通常基于以下几个步骤:

  1. Token 有效期:首先,服务器会为每个 token 设置一个有效期,例如 30 分钟或 1 小时。在这个有效期内,用户可以使用 token 访问受保护的资源。
  2. 定期检查:前端应用会在用户活动期间定期检查 token 的有效期。通常通过轮询或心跳机制实现,即在一定时间间隔后发送请求到服务器,检查 token 是否仍然有效。
  3. 刷新 Token:如果服务器返回 token 已失效的信息,前端应用会立即发起一个新的请求到认证服务器,使用refreshToken这个token(后端一般会返回一个长token和一个短token,长短指的是过期时间,refreshToken作为长token存在localstorage中用来向后端携带这个token去请求短token,accessToken作为短token也是存在localstorage中用这个token去请求受保护的请求放到请求头headers中)来获取新的访问 token。
  4. 无缝切换:在获取到新 token 后,前端应用会立即更新本地存储的 token,并使用新 token 继续之前的操作,从而实现了无缝的用户体验。

示例

下面是一个简单的示例,展示了如何在前端实现无感刷新 token:

  1. 设置 Token 有效期:假设服务器为每个 token 设置了 30 分钟的有效期。
  2. 定期检查:前端应用每 5 分钟向服务器发送一个请求,检查当前 token 是否仍然有效。
	// 使用 setInterval 定时发送心跳请求  

	setInterval(() => {  

	  checkTokenValidity();  

	}, 5 * 60 * 1000); // 5 分钟
  1. 检查 Token 有效性:前端应用发送心跳请求到服务器,服务器返回 token 是否有效的信息。
	async function checkTokenValidity() {  

	  try {  

	    const response = await fetch('/api/heartbeat', {  

	      headers: {  

	        Authorization: `Bearer ${localStorage.getItem('token')}`  

	      }  

	    });  

	  

	    if (!response.ok) {  

	      // Token 失效,需要刷新  

	      refreshToken();  

	    }  

	  } catch (error) {  

	    // 处理错误  

	    console.error('Error checking token validity:', error);  

	  }  

	}
  1. 刷新 Token:如果 token 失效,前端应用会发送一个请求到认证服务器,获取新的访问 token。
	async function refreshToken() {  

	  try {  

	    const response = await fetch('/api/auth/refresh', {  

	      method: 'POST',  

	      headers: {  

	        'Content-Type': 'application/json'  

	      },  

	      body: JSON.stringify({  

	        refreshToken: localStorage.getItem('refreshToken')  

	      })  

	    });  

	  

	    if (response.ok) {  

	      const data = await response.json();  

	      // 更新本地存储的 token  

	      localStorage.setItem('accessToken', data.accessToken);  

	    } else {  

	      // 处理刷新 token 失败的情况,例如提示用户重新登录  

	      console.error('Failed to refresh token:', response.status);  

	    }  

	  } catch (error) {  

	    // 处理错误  

	    console.error('Error refreshing token:', error);  

	  }  

	}

在这个示例中,前端应用通过定期检查 token 的有效性,并在 token 失效时无缝刷新 token,从而保持了用户的登录状态,并提供了无缝的用户体验。需要注意的是,为了安全起见,刷新 token 也应该有一定的有效期,并在过期后要求用户重新登录。

token还没有刷新完成时,发送了其他请求需要如何处理

如果刷新的 token 还没有返回,但是你已经使用旧的 token 发送了其他三个请求到服务端,那么可能会遇到以下几种情况:

请求被拒绝:如果服务端检查到 token 已经过期,它可能会返回一个 401 Unauthorized 或者类似的错误状态码。在这种情况下,你需要捕获这些错误,并在捕获到错误后尝试使用新的 token 重新发送这些请求。

为了处理这些情况,你可以采取以下策略:

  • 错误处理:确保你的应用能够捕获和处理 401 Unauthorized 或其他相关错误。当捕获到这些错误时,你可以尝试使用新的 token 重新发送请求。
  • 请求重试机制:实现一个请求重试机制,当请求失败时,可以自动或手动触发重试,并使用新的 token。你可以使用指数退避策略来避免频繁重试对服务端造成压力。
  • 状态管理:在 token 刷新期间,你可以将应用的状态设置为“正在刷新 token”,并禁止发送新的请求,直到获取到新的 token。这样可以避免使用无效的 token 发送更多请求。
  • 队列请求:在 token 过期后,而不是立即发送请求,你可以将请求放入队列中,等待新的 token 获取后再依次发送。这可以通过使用 Promise.all 或者其他异步处理技术来实现。
  • 前端提示:在 token 过期后,你可以在前端显示一个提示,告知用户正在刷新 token,并可能需要等待一段时间或者重新登录。 下面是一个简化的示例代码,

展示了如何在捕获到 401 错误后使用新 token 重新发送请求:

	// 假设这是你的 API 请求函数  

	async function apiRequest(url, token) {  

	  try {  

	    const response = await fetch(url, {  

	      headers: {  

	        Authorization: `Bearer ${token}`  

	      }  

	    });  

	  

	    if (!response.ok) {  

	      throw new Error(`Request failed with status ${response.status}`);  

	    }  

	  

	    return response.json();  

	  } catch (error) {  

	    if (error.message.includes('401') && newToken) {  

	      // 尝试使用新 token 重新发送请求  

	      return apiRequest(url, newToken);  

	    }  

	    throw error;  

	  }  

	}  

	  

	// 假设这是你的 token 刷新函数  

	let newToken = null; // 存储新 token 的变量  

	  

	async function refreshToken() {  

	  try {  

	    const response = await fetch('/api/auth/refresh', {  

	      // ... 发送刷新请求的代码 ...  

	    });  

	  

	    if (response.ok) {  

	      const data = await response.json();  

	      newToken = data.token;  

	    }  

	  } catch (error) {  

	    // 处理错误  

	  }  

	}  

	  

	// 当 token 过期时调用此函数  

	async function handleTokenExpiration() {  

	  try {  

	    // 尝试刷新 token  

	    await refreshToken();  

	  

	    // 假设这是之前因为 token 过期而失败的请求数组  

	    const failedRequests = [/* ... */];  

	  

	    // 使用新 token 重新发送请求  

	    for (const request of failedRequests) {  

	      try {  

	        const response = await apiRequest(request.url, newToken);  

	        // 处理响应或更新应用状态  

	      } catch (error) {  

	        // 处理重新发送请求时的错误  

	      }  

	    }  

	  } catch (error) {  

	    // 处理刷新 token 或重新发送请求时的错误  

	  }  

	}

在这个示例中,apiRequest 函数会在遇到 401 错误时检查是否有新的 token 可用,并尝试使用新 token 重新发送请求。handleTokenExpiration 函数则负责在 token 过期时刷新 token,并重新发送之前失败的请求。

队列请求

队列请求的实现主要是利用队列这种数据结构来管理待处理的请求。当某个请求因为某些原因(比如token过期)需要延迟处理时,我们可以将这个请求放入队列中,等待条件满足(比如获取到新的token)后再从队列中取出请求进行处理。

下面是一个简单的例子来说明队列请求的实现:

1. 创建请求队列

首先,我们需要一个队列来存储待处理的请求。这个队列可以是内存中的一个数组、链表,或者使用一些现成的队列库(如JavaScript中的Array.prototype.queuequeue-promise库)。

	let requestQueue = []; // 使用数组作为简单的队列实现

2. 入队操作

当需要延迟处理一个请求时,我们将这个请求添加到队列的末尾。

	function enqueueRequest(request) {  

	  requestQueue.push(request);  

	}

3. 出队操作

当条件满足(比如获取到新的token)时,我们从队列的头部取出一个请求进行处理,并重复这个过程直到队列为空。

	async function dequeueAndProcessRequests() {  

	  while (requestQueue.length > 0) {  

	    const request = requestQueue.shift(); // 从队列头部取出一个请求  

	    try {  

	      const response = await processRequest(request); // 处理请求  

	      // 处理响应或更新应用状态  

	    } catch (error) {  

	      // 处理请求处理过程中的错误  

	      console.error('Error processing request:', error);  

	    }  

	  }  

	}

4. 处理请求

processRequest函数负责实际处理请求的逻辑。在这个函数中,你可以发送HTTP请求、更新UI等。

	async function processRequest(request) {  

	  // 这里是处理请求的逻辑,比如发送HTTP请求  

	  const response = await fetch(request.url, {  

	    headers: {  

	      Authorization: `Bearer ${newToken}` // 使用新的token  

	    }  

	  });  

	  return response.json(); // 返回处理结果  

	}

5. 使用示例

当token过期时,我们可以将需要延迟处理的请求加入队列,然后尝试刷新token。一旦获取到新的token,我们从队列中取出请求并处理它们。

	// 假设这是因为token过期而需要延迟处理的请求  

	const delayedRequest = { url: '/api/data' };  

	  

	// 将请求加入队列  

	enqueueRequest(delayedRequest);  

	  

	// 尝试刷新token  

	await refreshToken();  

	  

	// 处理队列中的请求  

	await dequeueAndProcessRequests();

注意事项

  • 队列大小管理:如果请求队列变得非常大,可能会消耗大量内存。你可能需要实现一些策略来限制队列的大小,比如设置最大长度、淘汰最旧的请求等。
  • 错误处理:在处理请求和响应时,务必进行适当的错误处理,以避免应用崩溃或产生不可预期的行为。
  • 并发控制:如果你的应用需要处理大量并发请求,你可能需要实现一些机制来控制并发数,比如使用Promise.all限制同时处理的请求数量。
转载自:https://juejin.cn/post/7339741848078139430
评论
请登录