前端,你还不会刷新token吗?

一、什么情况下需要做token刷新?

背景:单点登录使用token方案

目的:减轻服务器压力

双token的好处:具有权限控制的能力

在单点登录情况下,使用token做权限验证,可以减轻服务器的压力。使用两个token,accessToken和refreshToken。当使用accessToken访问 其他服务过期时,需要去CAS 服务刷新token,然后再去请求业务服务器。

控制refreshToken,就可以达到踢人下线等目的。

CAS (Central Authentication Service)中心授权服务

单点登录(SSO:Single Sign On

用户只需要登录一次,就可以在多个系统中进行访问。类似于在百度系网站中,你在百度网页登录了百度账号,旗下的贴吧,网盘等不需要再次登录,就可以使用。

二、如何实现token刷新?

画板

优化:使用队列优化未发出的请求

当已经出现未授权错误时,之后进入的请求都放入到一个队列中,当刷新token成功后,修改token,再发出,达到减轻服务器压力的目的

import { isAxiosError, type AxiosError, type AxiosResponse } from 'axios';
import type { FReqConfig, ReqItem, SetupRefreshTokenOptions } from './type';

export function useRefreshToken({
  refreshToken,
  unauthorizedErrorHandler,
  http,
  setToken,
  getIsUnauthorized
}: SetupRefreshTokenOptions) {
  
  let promise: Promise<any> | null = null;
  let isRefreshToken = false;

  /**
   * ## 收集401失败请求,如果没有正在刷新token,则刷新
   * @param resp
   */
  async function handleUnauthorized(resp: AxiosResponse | AxiosError) {

    const { status, config } = resp || {};
    let isUnauthorized = false;
    const isError = await isAxiosError(resp);
    
    if (isError) {
      isUnauthorized = status === 401;
    } else {
      if (typeof getIsUnauthorized == 'function') {
        isUnauthorized = getIsUnauthorized(resp);
      }
    }

    // 如果当前正在刷新中,则返回promise
    if (isUnauthorized) {
      if (!isRefreshToken) _refreshToken();
      
      const isSuccess = await promise;
      if (isSuccess) {
        return http.request(setToken(config!));
      } else {
        if (typeof unauthorizedErrorHandler === 'function') unauthorizedErrorHandler();

        return Promise.reject(resp);
      }
    }

    // 不是未授权错误
    if (isError) {
      return Promise.reject(resp);
    } else {
      return Promise.resolve(resp);
    }
  }

  /** ## 收集 401 后发起的请求,等token刷新后再执行 */
  let queue: ReqItem[] = [];

  /**
   * # 当刷新token时,收集将要执行的请求
   * @param config
   * @returns axiosConfig
   */
  function handleReqWhenUnauthorized(config: FReqConfig): Promise<FReqConfig> {
    if (isRefreshToken) {
      return new Promise((resolve) => {
        queue.push({ resolve, config, type: 'request' });
      });
    }

    return Promise.resolve(config);
  }

  /**
   * ## 处理刷新token的逻辑
   * 1. 所有的401 请求都使用一个 刷新token的 promise
   * 2. 刷新token成功后,重新执行因为401 而没有发起的请求
   */
  async function _refreshToken() {
    isRefreshToken = true;
    if (!promise) {
      promise = refreshToken();
    }

    promise.then((isSuccess) => {
      if (isSuccess)
        queue.forEach((item) => {
          item.resolve(setToken(item.config));
        });
    });

    // 重置刷新标识, 刷新的promise, 和 待发送请求的队列
    promise.finally(() => {
      isRefreshToken = false;
      promise = null;
      queue = [];
    });
  }

  return { handleUnauthorized, handleReqWhenUnauthorized };
}

参考资料

前端实现无感刷新 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]。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值