前端无感刷新token

news2024/11/18 13:20:02

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

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.coloradmin.cn/o/2242832.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【AI图像生成网站Golang】雪花算法

AI图像生成网站 目录 一、项目介绍 二、雪花算法 三、JWT认证与令牌桶算法 四、项目架构 五、图床上传与图像生成API搭建 六、项目测试与调试(等待更新) 雪花算法 雪花算法 (Snowflake) 是一种高效、可扩展的分布式唯一ID生成算法,最早由 Twitter 开发&…

[369]基于springboot的高校教师教研信息填报系统

摘 要 如今社会上各行各业,都喜欢用自己行业的专属软件工作,互联网发展到这个时候,人们已经发现离不开了互联网。新技术的产生,往往能解决一些老技术的弊端问题。因为传统高校教师教研信息填报系统信息管理难度大,容错…

《素书》:为上计,事当缓;为下计,事当急

《素书》是中国古代一部重要的典籍,相传为秦末黄石公作,内容主要讲述为人处世的道理。其中,“为上计,事当缓;为下计,事当急”这句话,给人以深刻的启示。 首先,对于上策之事&#xf…

树莓派4B Qt+FFMPEG 多线程录制USB相机mjpeg数据流“h264_omx“硬件编码的MP4文件

文章目录 1 前言2 一些问题说明2.0 树莓派4b系统版本2.1 Qt2.2 FFMPEG2.3 图像格式 3 核心代码3.0 代码逻辑3.1 pro文件3.2 avframequeue.cpp3.3 decodethread.cpp 4 资源下载 1 前言 本项目为在树莓派4B开发板上,通过QtFFMPEG以多线程分别解码、编码USB摄像头视频数…

排序算法(基础)大全

一、排序算法的作用: 排序算法的主要作用是将一组数据按照特定的顺序进行排列,使得数据更加有序和有组织。 1. 查找效率:通过将数据进行排序,可以提高查找算法的效率。在有序的数据中,可以使用更加高效的查找算法&…

计算机网络:运输层 —— TCP 的拥塞控制

文章目录 TCP的拥塞控制拥塞控制的基本方法流量控制与拥塞控制的区别拥塞控制分类闭环拥塞控制算法 TCP的四种拥塞控制方法(算法)窗口慢开始门限慢开始算法拥塞避免算法快重传算法快恢复算法 TCP拥塞控制的流程TCP拥塞控制与网际层拥塞控制的关系 TCP的拥…

如何在Mysql中生成0-23完整的小时数据

目录 1. 创建表2. 插入0-23小时的数据3. 查询并合并数据 在数据分析中,我们经常需要对特定时间段内的数据进行统计和分析。 例如,在名片进线的场景中,我们可能需要了解一天内每小时的名片进线数量。 然而,由于某些时间点可能没有数…

【GeekBand】C++设计模式笔记12_Singleton_单件模式

1. “对象性能” 模式 面向对象很好地解决了 “抽象” 的问题, 但是必不可免地要付出一定的代价。对于通常情况来讲,面向对象的成本大都可以忽略不计。但是某些情况,面向对象所带来的成本必须谨慎处理。典型模式 SingletonFlyweight 2. Si…

架构篇(理解架构的模式2)

目录 一、管理和监控 大使模式:创建代表消费者服务或应用程序发送网络请求的帮助服务 反腐模式:在现代应用程序和遗留系统之间实现装饰或适配器层 外部配置存储:将应用程序部署包中的配置信息移动到中心化的位置 网关聚合模式&#xff1…

20241116解决在WIN11和ubuntu20.04通过samba共享时出现局域网千兆带宽拉满的情况

20241116解决在WIN11和ubuntu20.04通过samba共享时出现局域网千兆带宽拉满的情况 2024/11/16 13:42 缘起:最近需要通过iperf3打流,因此在ubuntu20.04服务器上常开sudo nethogs监控流量。 但是发现一个异常,ubuntu20.04服务器上发送的流量过大…

DevOps工程技术价值流:打造卓越项目协作的优化宝典

一、引言 解锁项目协作的无限潜力,覆盖全链路实现流畅高效。 在当今瞬息万变的商业环境中,项目协作的效率和效果直接关系到企业的竞争力和市场响应速度。DevOps工程技术价值流中的项目协作优化,不仅是技术层面的革新,更是团队协…

WSL--无需安装虚拟机和docker可以直接在Windows操作系统上使用Linux操作系统

安装WSL命令 管理员打开PowerShell或Windows命令提示符,输入wsl --install,然后回车 注意:此命令将启用运行 WSL 和安装 Linux 的 Ubuntu 发行版所需的功能。 注意:默认安装最新的Ubuntu发行版。 注意:默认安装路径是…

⾃动化运维利器Ansible-基础

Ansible基础 一、工作原理二、快速入门2.1 测试所有资产的网络连通性2.2 发布文件到被管理节点(资产) 三、资产(被管理节点)3.1 静态资产3.1.1 自定义资产3.1.2 自定义资产的使用3.1.3 资产选择器 四、Ansible Ad-Hoc 命令4.1 模块类型4.1.1 command & shell 模块4.1.2 cop…

简易实现自动签到并发送通知邮件

环境准备 Windows操作系统adbshell1.0.40pyhon3.7visual stdio code stableandroid手机数据线,并配置环境变量 打卡程序 需要定位屏幕坐标 import os import timea0os.popen("adb shell input keyevent 26") ##ba0.read() ##print(b) time.sleep(5) …

机器学习(贝叶斯算法,决策树)

朴素贝叶斯分类 贝叶斯分类理论 假设现有两个数据集,分为两类 我们现在用p1(x,y)表示数据点(x,y)属于类别1(图中红色圆点表示的类别)的概率,用p2(x,y)表示数据点(x,y)属于类别2(图中蓝色三角形表示的类别)的概率,那么对于一个新数据点(x,y)…

[ACTF2020]Upload 1--详细解析

信息收集 题目告诉我们是一道upload,也就是文件上传漏洞题目。 进入界面,是一个灯泡,将鼠标放在图标上就会出现文件上传的相应位置: 思路 文件上传漏洞,先看看有没有前端校验。 在js源码中找到了前端校验&#xff…

网络常用特殊地址-127.0.0.1

借用Medium博客的一张图 经常在问题解答群里留意到如下关于127.0.0.1的消息 ”如果单机版,不需要配置IP,所有配置IP的地方都写死127.0.0.1就可以” “ip: 根据实际情况填写(在 xxx-init.conf 里可以给一个默认值 127.0.0.1 ,方便…

Scala-字符串(拼接、printf格式化输出等)-用法详解

Scala 一、 使用 号连接字符串 在 Scala 中, 运算符实际上会调用 String 类的 concat 方法或者使用字符串的加法操作,生成一个新的字符串。 字符串是不可变的,每次拼接都会创建一个新的字符串。 Mr. yuTips: 性能相对较差&…

软考教材重点内容 信息安全工程师 第 4 章 网络安全体系与网络安全模型

4,1 网络安全体系的主要特征: (1)整体性。网络安全体系从全局、长远的角度实现安全保障,网络安全单元按照一定的规则,相互依赖、相互约束、相互作用而形成人机物一体化的网络安全保护方式。 (2)协同性。网络安全体系依赖于多种安全机制,通过各…

【数据库】如何保证数据库迁移过程中数据的一致性?

在数据库迁移过程中,保证数据的一致性是非常重要的,尤其是在涉及到多个表、多个数据库或分布式系统的情况下。以下是一些确保数据一致性的最佳实践和方法: 1. 备份数据 在开始迁移之前,进行全面的数据备份是确保数据一致性的第…