当前位置: 首页 > article >正文

前端无感刷新token

在这里插入图片描述
摘要:

Axios 无感知刷新令牌是一种在前端应用中实现自动刷新访问令牌(access token)的技术,确保用户在进行 API 请求时不会因为令牌过期而中断操作

目录概览

    • XMLHttpRequest
    • Axios
    • Fetch API
    • JQ
    • uni.request
    • 注意事项:

  • 访问令牌(Access Token):用于访问受保护资源的凭证,通常有一定的有效期。
  • 刷新令牌(Refresh Token):用于获取新的访问令牌,当访问令牌过期时使用。

实现步骤:

  1. 设置拦截器:在 Axios的请求拦截器中添加逻辑,检查当前时间与令牌的过期时间。如果访问令牌已过期但刷新令牌仍然有效,则调用刷新令牌接口获取新的访问令牌。
  2. 更新令牌存储:一旦获得新的访问令牌,将其存储到 localStorage、Vuex 或其他状态管理工具中,以便后续请求使用新令牌。
  3. 重试原始请求:在成功刷新令牌后,重新发送被拦截的请求,此时使用新的访问令牌。

XMLHttpRequest

// 创建 XMLHttpRequest 实例
const xhr = new XMLHttpRequest();

// 登录成功后保存 Token 和 Refresh Token
function onLoginSuccess(response) {
    localStorage.setItem('accessToken', response.data.accessToken);
    localStorage.setItem('refreshToken', response.data.refreshToken);
}

// 发起请求的函数
function sendRequest(url, method, data) {
    return new Promise((resolve, reject) => {
        xhr.open(method, url);
        xhr.setRequestHeader('Authorization', `Bearer ${localStorage.getItem('accessToken')}`);
        xhr.onreadystatechange = function() {
            if (xhr.readyState === 4) {
                if (xhr.status === 200) {
                    resolve(JSON.parse(xhr.responseText));
                } else {
                    reject({ status: xhr.status, response: xhr.responseText });
                }
            }
        };
        if (method === 'POST' && data) {
            xhr.send(JSON.stringify(data));
        } else {
            xhr.send();
        }
    });
}

// 刷新 Token 的函数
async function refreshToken() {
    const refreshToken = localStorage.getItem('refreshToken');
    const response = await fetch('/path/to/refresh', {
        method: 'POST',
        headers: {
            'Content-Type': 'application/json',
        },
        body: JSON.stringify({ refresh_token: refreshToken }),
    });
    const res = await response.json();
    if (res.success) {
        localStorage.setItem('accessToken', res.data.newAccessToken);
        return true; // 表示刷新成功
    } else {
        return false; // 表示刷新失败
    }
}

// 拦截响应并处理 Token 刷新
xhr.addEventListener('readystatechange', function() {
    if (xhr.readyState === 4 && xhr.status === 401) {
        refreshToken().then(refreshed => {
            if (refreshed) {
                xhr.setRequestHeader('Authorization', `Bearer ${localStorage.getItem('accessToken')}`);
                xhr.send(); // 重新发送请求
            } else {
                alert('请重新登录'); // Token 刷新失败,可能需要用户重新登录
            }
        });
    }
});

Axios

import axios from 'axios';

// 创建 Axios 实例
const apiClient = axios.create({
  baseURL: 'https://your-api-url.com',
  // 其他配置...
});

// 响应拦截器
apiClient.interceptors.response.use(response => {
  return response;
}, error => {
  const { response } = error;
  if (response && response.status === 401) {
    return refreshToken().then(refreshed => {
      if (refreshed) {
        // 令牌刷新成功,重试原始请求
        return apiClient.request(error.config);
      } else {
        // 令牌刷新失败,可能需要用户重新登录
        return Promise.reject(error);
      }
    });
  }
  return Promise.reject(error);
});

// 令牌刷新函数
function refreshToken() {
  return apiClient.post('/path/to/refresh', {
    // 刷新令牌所需的参数,例如 refresh_token
  }).then(response => {
    if (response.data.success) {
      // 假设响应数据中包含新的访问令牌
      const newAccessToken = response.data.newAccessToken;
      // 更新令牌存储
      localStorage.setItem('accessToken', newAccessToken);
      // 更新 Axios 实例的 headers,以便后续请求使用新令牌
      apiClient.defaults.headers.common['Authorization'] = `Bearer ${newAccessToken}`;
      return true; // 表示刷新成功
    } else {
      return false; // 表示刷新失败
    }
  });
}

Fetch API

// 定义一个函数来处理Fetch请求
async function fetchWithToken(url, options = {}) {
    const token = localStorage.getItem('token');
    if (token) {
        options.headers = {
            ...options.headers,
            'Authorization': `Bearer ${token}`
        };
    }

    try {
        const response = await fetch(url, options);
        if (response.status === 401) { // 假设401表示令牌过期
            const refreshToken = localStorage.getItem('refreshToken');
            if (!refreshToken) {
                throw new Error('No refresh token available');
            }

            // 调用刷新令牌接口
            const refreshResponse = await fetch('/api/refresh-token', {
                method: 'POST',
                headers: {
                    'Content-Type': 'application/json'
                },
                body: JSON.stringify({ refreshToken })
            });

            if (refreshResponse.ok) {
                const data = await refreshResponse.json();
                localStorage.setItem('token', data.newAccessToken);
                // 重新尝试原始请求
                options.headers['Authorization'] = `Bearer ${data.newAccessToken}`;
                return fetch(url, options);
            } else {
                throw new Error('Failed to refresh token');
            }
        }
        return response;
    } catch (error) {
        console.error('Fetch error:', error);
        throw error;
    }
}

// 使用示例
fetchWithToken('/api/protected-resource')
    .then(response => response.json())
    .then(data => console.log(data))
    .catch(error => console.error('Error:', error));

  • fetchWithToken函数: 这是一个封装了Fetch API的函数,它首先检查本地存储中的访问令牌是否存在,并在请求头中添加该令牌。如果响应状态码为401(表示令牌过期),则尝试使用刷新令牌获取新的访问令牌,并重新发送原始请求。
  • 刷新令牌逻辑: 在检测到令牌过期时,函数会调用刷新令牌接口,并将新的访问令牌存储到本地存储中。然后,它会重新设置请求头中的授权信息,并重新发送原始请求。
  • 错误处理: 如果在刷新令牌或发送请求的过程中发生错误,函数会抛出相应的错误,并在控制台中记录错误信息。

JQ

// 创建 JQuery 实例
const apiClient = $.ajaxSetup({
  baseURL: 'https://your-api-url.com',
  // 其他配置...
});

// 响应拦截器
$.ajaxSetup({
  complete: function(jqXHR, textStatus) {
    if (textStatus === 'error' && jqXHR.status === 401) {
      return refreshToken().then(refreshed => {
        if (refreshed) {
          // 令牌刷新成功,重试原始请求
          return apiClient.request(this);
        } else {
          // 令牌刷新失败,可能需要用户重新登录
          alert('请重新登录');
        }
      });
    }
  }
});

// 令牌刷新函数
function refreshToken() {
  return $.ajax({
    url: '/path/to/refresh',
    method: 'POST',
    data: {
      refresh_token: localStorage.getItem('refreshToken')
    },
    dataType: 'json'
  }).then(response => {
    if (response.data.success) {
      // 假设响应数据中包含新的访问令牌
      const newAccessToken = response.data.newAccessToken;
      // 更新令牌存储
      localStorage.setItem('accessToken', newAccessToken);
      // 更新 JQuery 实例的 headers,以便后续请求使用新令牌
      apiClient.defaults.headers.common['Authorization'] = `Bearer ${newAccessToken}`;
      return true; // 表示刷新成功
    } else {
      return false; // 表示刷新失败
    }
  });
}

uni.request

// 导入封装的request插件
import http from './interface';
import { getRefreshToken } from '@/common/api/apis.js'; // 刷新token接口

let isRefreshing = false; // 是否处于刷新token状态中
let fetchApis = []; // 失效后同时发送请求的容器
let refreshCount = 0; // 限制无感刷新的最大次数

function onFetch(newToken) {
  refreshCount += 1;
  if (refreshCount === 3) {
    refreshCount = 0;
    fetchApis = [];
    return Promise.reject();
  }
  fetchApis.forEach(callback => {
    callback(newToken);
  });
  // 清空缓存接口
  fetchApis = [];
  return Promise.resolve();
}

// 响应拦截器
http.interceptor.response((response) => {
  if (response.config.loading) {
    uni.hideLoading();
  }
  // 请求成功但接口返回的错误处理
  if (response.data.statusCode && +response.data.statusCode !== 200) {
    if (!response.config.needPromise) {
      console.log('error', response);
      uni.showModal({
        title: '提示',
        content: response.data.message,
        showCancel: false,
        confirmText: '知道了'
      });
      // 中断
      return new Promise(() => {});
    } else {
      // reject Promise
      return Promise.reject(response.data);
    }
  }
  return response;
}, (error) => {
  const token = uni.getStorageSync('token');
  const refreshToken = uni.getStorageSync('refreshToken');
  // DESC: 不需要做无感刷新的白名单接口
  const whiteFetchApi = ['/dealersystem/jwtLogin', '/dealersystem/smsLogin', '/sso2/login', '/dealersystem/isLogin'];
  switch (error.statusCode) {
    case 401:
    case 402:
      if (token && !whiteFetchApi.includes(error.config.url)) {
        if (!isRefreshing) {
          isRefreshing = true;
          getRefreshToken({ refreshToken }).then(res => {
            let newToken = res.data;
            onTokenFetched(newToken).then(res => {}).catch(err => {
              // 超过循环次数时,回到登录页,这里可以添加你执行退出登录的逻辑
              uni.showToast({ title: '登录失效,请重新登录', icon: 'error' });
              setTimeout(() => {
                uni.reLaunch({ url: '/pages/login/login' });
              }, 1500);
            });
          }).catch(err => {
            // refreshToken接口报错,证明refreshToken也过期了,那没办法啦重新登录呗
            uni.showToast({ title: '登录失效,请重新登录', icon: 'error' });
            setTimeout(() => {
              uni.reLaunch({ url: '/pages/login/login' });
            }, 1500);
          }).finally(() => { isRefreshing = false });
        }
        return new Promise((resolve) => { // 此处的promise很关键,就是确保你的接口返回值在此处resolve,以便后续代码执行
          addFetchApi((newToken) => {
            error.config.header['Authorization'] = `Bearer ${newToken}`;
            http.request(error.config).then(response => {
              resolve(response);
            });
          });
        });
      }
      break;
    default:
      break;
  }
});

注意事项:

  • 错误处理:确保在刷新令牌失败时,有适当的错误处理机制,例如提示用户重新登录。
  • 并发请求:处理多个请求同时需要刷新令牌的情况,避免重复刷新。
  • 安全性:确保刷新令牌的安全存储和传输,防止被恶意攻击者获取。

http://www.kler.cn/a/397390.html

相关文章:

  • SAFETY LAYERS IN ALIGNED LARGE LANGUAGEMODELS: THE KEY TO LLM SECURITY
  • GitHub Copilot使用指南:助力开发者加速编程创新
  • 每天五分钟机器学习:支持向量机算法数学基础之核函数
  • [Linux]多线程详解
  • Nginx 上安装 SSL 证书并启用 HTTPS 访问
  • 解决部署RKE2或K3S-“docker.io/rancher/mirrored-pause:3.6\“: -无法拉取镜像办法
  • Vue的局部使用
  • 腾讯IM uniapp微信小程序版本实现迅飞语音听写(流式版)
  • 【机器学习chp2】贝叶斯最优分类器、概率密度函数的参数估计、朴素贝叶斯分类器、高斯判别分析。万字超详细分析总结与思考
  • Typora右键打开文件夹/设置右键打开方式/Windows右键管理器
  • 源码解析-Spring Eureka(更新ing)
  • HTML面试题(2)
  • 前端性能优化之R树的使用
  • IDEA自定义文件打开格式
  • 【蓝桥等考C++真题】蓝桥杯等级考试C++组第13级L13真题原题(含答案)-套娃
  • 读书笔记:《Redis设计与实现》之发布订阅
  • Restful API接⼝简介及为什么要进⾏接⼝压测
  • 【python】掌握 Flask:轻量级 Web 开发框架解析
  • 理论力学基础:讲义与笔记(1)
  • llamaindex实战-Agent-在Agent中和数据库对话(本地部署)
  • 新人如何做好项目管理?|京东零售技术人成长
  • web H5网页中嵌入优量汇的插屏广告
  • 爬虫——Requests库的使用
  • YOLOv8改进,YOLOv8通过RFAConv卷积创新空间注意力和标准卷积,包括RFCAConv, RFCBAMConv,二次创新C2f结构,助力涨点
  • day-83 最少翻转次数使二进制矩阵回文 II
  • 循环神经网络(RNN)全面解析