Skip to content

前端实现无感刷新 Token 使用 Axios ​

alt text

在现代的前端开发中,离我们最近的就是微信JSSDK,除此很多应用都需要使用 Access Token 来访问受保护的资源。为了保证安全性,Access Token 通常具有有限的有效期,这就需要使用 Refresh Token 来无感刷新 Access Token,保持用户会话的持续性。

本文将介绍如何利用 Axios 实现无感刷新 Token,使得用户在前端使用过程中不需要手动重新登录或者感知 token 的刷新。

1. 概述 ​

在应用中,用户的登录会话通常包括 Access Token 和 Refresh Token。Access Token 用于访问受保护的 API,而 Refresh Token 用于在 Access Token 过期时,自动向服务器请求新的 Access Token。

为了实现无感刷新的机制,我们将利用 Axios 拦截器,自动检测 Access Token 是否过期并进行刷新操作。

2. 实现步骤 ​

2.1 设计流程 ​

用户第一次登录后,后端返回 Access Token 和 Refresh Token。 当用户发送 API 请求时,前端将 Access Token 附加在请求头中。 当 Access Token 过期时,使用 Refresh Token 请求新的 Access Token。 如果刷新成功,更新本地存储中的 Access Token,并重试之前的请求。 如果刷新失败(如 Refresh Token 过期),则用户需要重新登录。

2.2 前端代码实现 ​

2.2.1 设置 Axios 实例 ​

首先,我们需要创建一个 Axios 实例,并配置全局的请求拦截器和响应拦截器。

js
import axios from "axios";
import {
  getAccessToken,
  getRefreshToken,
  setAccessToken,
  removeTokens,
} from "./tokenUtils"; // 你需要实现这部分方法

// 创建 axios 实例
const axiosInstance = axios.create({
  baseURL: "https://api.example.com",
  timeout: 10000, // 设置请求超时时间
});

// 请求拦截器:为每个请求添加 Access Token
axiosInstance.interceptors.request.use(
  (config) => {
    const token = getAccessToken(); // 获取当前存储的 Access Token
    if (token) {
      config.headers["Authorization"] = `Bearer ${token}`; // 在请求头中添加 Access Token
    }
    return config;
  },
  (error) => Promise.reject(error),
);

// 响应拦截器:处理 token 刷新逻辑
axiosInstance.interceptors.response.use(
  (response) => response, // 如果响应成功,直接返回数据
  async (error) => {
    const originalRequest = error.config;
    // 判断是否因为 token 过期(假设 401 错误表示过期)
    if (
      error.response &&
      error.response.status === 401 &&
      !originalRequest._retry
    ) {
      originalRequest._retry = true; // 防止重复刷新
      try {
        // 使用 Refresh Token 刷新 Access Token
        const newAccessToken = await refreshToken();
        setAccessToken(newAccessToken); // 更新本地的 Access Token
        originalRequest.headers["Authorization"] = `Bearer ${newAccessToken}`; // 更新请求头中的 Access Token
        return axiosInstance(originalRequest); // 重试原始请求
      } catch (refreshError) {
        // 如果刷新失败,则清除所有 token,并引导用户重新登录
        removeTokens();
        window.location.href = "/login"; // 重定向到登录页面
        return Promise.reject(refreshError);
      }
    }
    return Promise.reject(error);
  },
);

// 刷新 Access Token
async function refreshToken() {
  const refreshToken = getRefreshToken(); // 获取存储的 Refresh Token
  const response = await axiosInstance.post("/auth/refresh", {
    refresh_token: refreshToken,
  });
  return response.data.access_token; // 假设服务器返回新的 Access Token
}

export default axiosInstance;
2.2.2 工具函数:获取和存储 Token ​

你需要实现一些方法来获取、存储和删除 Access Token 和 Refresh Token。通常这些 token 会存储在 localStorage 或 sessionStorage 中。

js
// tokenUtils.js

// 获取 Access Token
export function getAccessToken() {
  return localStorage.getItem("access_token");
}

// 获取 Refresh Token
export function getRefreshToken() {
  return localStorage.getItem("refresh_token");
}

// 设置 Access Token
export function setAccessToken(token) {
  localStorage.setItem("access_token", token);
}

// 删除所有 token
export function removeTokens() {
  localStorage.removeItem("access_token");
  localStorage.removeItem("refresh_token");
}

2.3 前端实现的关键点 ​

请求拦截器:每次发送请求时,我们会在请求头中添加 Access Token,确保用户能够访问受保护的 API。 响应拦截器:当服务器返回 401 错误时(即 Access Token 过期),会自动触发刷新流程,使用 Refresh Token 请求新的 Access Token 并重试原始请求。 刷新逻辑:在刷新 Token 时,服务器会验证 Refresh Token 是否有效。如果有效,则返回新的 Access Token,如果无效,则清除所有 tokens 并引导用户重新登录。

处理跨域请求 ​

如果你的后端 API 与前端应用部署在不同的域中,请确保在后端开启了 CORS(跨域资源共享),并且允许携带 cookie 或 Authorization Header。

错误处理 ​

在进行 token 刷新时,可能会发生一些错误(如 Refresh Token 无效、网络请求失败等)。这些错误需要被合理处理,避免导致用户的登录会话丢失。上述代码中,如果刷新失败,用户会被重定向到登录页面。

总结 ​

通过利用 Axios 拦截器,我们可以实现一个 无感刷新 Token 的机制,确保用户在 Access Token 过期 时,应用能够自动获取新的 Access Token,而不需要用户干预。这个过程是透明的,用户不会感知到任何登录状态的改变。

请求拦截器 用于为每个请求添加 Access Token。 响应拦截器 用于捕获 401 错误,并在 Access Token 过期时,使用 Refresh Token 刷新 token 并重试请求。 如果刷新失败,用户会被引导到登录页面,保证了安全性。 这种机制可以有效提高用户体验,避免频繁的重新登录操作,同时确保系统的安全性。