实现Token无感刷新

发布于:2025-03-20 ⋅ 阅读:(21) ⋅ 点赞:(0)

在前端开发中,Token 无感刷新是一种常见的优化技术,用于在用户无感知的情况下刷新过期的身份验证 Token,从而避免用户因 Token 过期而需要重新登录。以下是实现 Token 无感刷新的思路和具体实现方法。

实现思路

Token 过期机制:

通常,身份验证 Token 有一个有效期(如 2 小时)。
当 Token 过期后,用户需要重新登录或刷新 Token。

无感刷新的核心:

在 Token 过期前,通过刷新 Token 接口获取新的 Token。
使用新的 Token 替换旧的 Token,并继续用户的请求。

实现步骤:

在请求拦截器中检查 Token 是否即将过期。
如果 Token 即将过期,发起刷新 Token 的请求。
在响应拦截器中处理 Token 过期的情况,重新发起失败的请求。

具体实现

以下是一个基于 Axios 的 Token 无感刷新实现示例:

  1. 安装 Axios
    如果尚未安装 Axios,可以通过以下命令安装:
npm install axios
  1. 封装 Axios 实例
    创建一个封装了 Token 无感刷新逻辑的 Axios 实例。
import axios from "axios";

// 创建 Axios 实例
const instance = axios.create({
  baseURL: "https://api.example.com",
  timeout: 10000,
});

// 存储 Token 和刷新 Token
let token = localStorage.getItem("token") || "";
let refreshToken = localStorage.getItem("refreshToken") || "";

// 请求拦截器
instance.interceptors.request.use(
  (config) => {
    // 如果 Token 存在,添加到请求头
    if (token) {
      config.headers.Authorization = `Bearer ${token}`;
    }
    return config;
  },
  (error) => {
    return Promise.reject(error);
  }
);

// 响应拦截器
instance.interceptors.response.use(
  (response) => {
    return response;
  },
  async (error) => {
    const originalRequest = error.config;

    // 如果 Token 过期且未发起过刷新请求
    if (error.response.status === 401 && !originalRequest._retry) {
      originalRequest._retry = true; // 标记为已发起刷新请求

      try {
        // 发起刷新 Token 的请求
        const response = await axios.post("/refresh-token", {
          refreshToken,
        });

        // 更新 Token 和刷新 Token
        const { token: newToken, refreshToken: newRefreshToken } = response.data;
        token = newToken;
        refreshToken = newRefreshToken;
        localStorage.setItem("token", newToken);
        localStorage.setItem("refreshToken", newRefreshToken);

        // 更新请求头中的 Token
        originalRequest.headers.Authorization = `Bearer ${newToken}`;

        // 重新发起原始请求
        return instance(originalRequest);
      } catch (refreshError) {
        // 刷新 Token 失败,跳转到登录页
        localStorage.removeItem("token");
        localStorage.removeItem("refreshToken");
        window.location.href = "/login";
        return Promise.reject(refreshError);
      }
    }

    return Promise.reject(error);
  }
);

export default instance;
  1. 使用封装的 Axios 实例
    在项目中使用封装好的 Axios 实例发起请求。
import axiosInstance from "./axiosInstance";

const fetchData = async () => {
  try {
    const response = await axiosInstance.get("/data");
    console.log(response.data);
  } catch (error) {
    console.error("请求失败", error);
  }
};

fetchData();

关键点解析

Token 存储:

将 Token 和刷新 Token 存储在 localStorage 或 sessionStorage 中。
每次请求时从存储中读取 Token。

请求拦截器:

在请求拦截器中,将 Token 添加到请求头。

响应拦截器:

在响应拦截器中,检查响应状态码是否为 401(未授权)。
如果 Token 过期,发起刷新 Token 的请求。
刷新成功后,更新 Token 并重新发起原始请求。

刷新 Token 接口:

后端需要提供一个刷新 Token 的接口(如 /refresh-token),接收 refreshToken 并返回新的 token 和 refreshToken。

错误处理:

如果刷新 Token 失败,清除本地存储的 Token 并跳转到登录页。

进一步优化

Token 过期时间检查:

在请求拦截器中检查 Token 的剩余有效期。
如果 Token 即将过期(如剩余 5 分钟),提前刷新 Token。

const isTokenExpired = (token) => {
  const payload = JSON.parse(atob(token.split(".")[1]));
  const exp = payload.exp * 1000; // 转换为毫秒
  return Date.now() >= exp - 5 * 60 * 1000; // 提前 5 分钟刷新
};

instance.interceptors.request.use(
  (config) => {
    if (token && isTokenExpired(token)) {
      // 发起刷新 Token 的请求
      refreshToken()
        .then((newToken) => {
          token = newToken;
          localStorage.setItem("token", newToken);
          config.headers.Authorization = `Bearer ${newToken}`;
        })
        .catch(() => {
          localStorage.removeItem("token");
          window.location.href = "/login";
        });
    }
    return config;
  },
  (error) => {
    return Promise.reject(error);
  }
);

并发请求处理:

如果多个请求同时发现 Token 过期,确保只发起一次刷新 Token 的请求。
使用一个标志变量(如 isRefreshing)来控制刷新请求的并发。

安全性:

使用 HTTPS 加密传输 Token。
设置合理的 Token 有效期和刷新 Token 的有效期。

总结

通过 Axios 的请求拦截器和响应拦截器,可以实现 Token 的无感刷新,从而提升用户体验。关键在于:
在 Token 过期前主动刷新。
处理并发请求和错误情况。
确保 Token 存储和传输的安全性。
这种方法适用于大多数前后端分离的项目,能够有效减少用户因 Token 过期而需要重新登录的情况。


网站公告

今日签到

点亮在社区的每一天
去签到