Vue:双token无感刷新
文章目录
- 初次授权与发放Token:
- Access Token的作用:
- Refresh Token的作用:
- 无感刷新:
- 安全机制:
- 后端创建nest项目
- AppController 添加login、refresh、getinfo接口
- 创建user.dto.ts
- AppController添加模拟数据
- 前端Hbuilder创建VUE3项目
- 安装axios
- 根目录下添加.env配置环境
- 根目录下创建vite.config.js配置代理
双token机制,尤其是指在OAuth 2.0授权协议中广泛使用的access token(访问令牌)和refresh token(刷新令牌)组合,用来实现无感刷新登录状态的原理如下:
初次授权与发放Token:
用户登录时,通过用户名、密码或其他认证方式向认证服务器请求授权。认证成功后,服务器不仅返回一个短期有效的access token(通常几分钟到几小时),还会发放一个长期有效的refresh token(几天到几个月)。
Access Token的作用:
access token是客户端访问受保护资源的临时凭证,每次客户端发起对受保护资源的请求时,都需要在HTTP请求头中携带access token。一旦access token过期,请求就会失败。
Refresh Token的作用:
refresh token的目的是在access token过期后,无需用户重新登录,客户端可以使用refresh token向认证服务器申请新的access token。通常refresh token的生命周期较长,而且存储得更为安全,因为它涉及到长期的授权。
无感刷新:
当客户端检测到access token即将过期或已经过期时,自动在后台向认证服务器发起请求,携带refresh token换取新的access token。这个过程对用户来说是无感知的,即用户不需要重新登录,页面也不会中断或刷新,因此被称为“无感刷新”。
安全机制:
为了保证安全性,refresh token一般具备一定的安全措施,例如限制其使用次数(防止无限刷新)、设置有效期(过期后必须重新登录)以及严格的存储策略(通常不会在客户端明文存储,而是存储在服务器端或经过加密存储在客户端本地)。
通过这种双token机制,可以在保障用户隐私和安全性的同时,大大提升用户体验,让用户在长时间操作过程中无需反复登录,实现所谓的“无感刷新登录状态”。
下载完整例子源码(vue+nest):https://download.csdn.net/download/ruancexiaoming/88913949
后端创建nest项目
# 创建
npx nest new token-test
#运行
cd token-test
npm run start
AppController 添加login、refresh、getinfo接口
// 登录请求@Post('api/login')login(@Body() userDto: UserDto) {console.log(userDto);const user = users.find(item => item.username === userDto.username);if (!user) {throw new BadRequestException('用户不存在');}if (user.password !== userDto.password) {throw new BadRequestException("密码错误");}const accessToken = this.jwtService.sign({username: user.username,email: user.email}, {expiresIn: '0.5h'});//access_token 过期时间半小时const refreshToken = this.jwtService.sign({username: user.username}, {expiresIn: '7d'})//refresh_token 过期时间 7 天return {userInfo: {username: user.username,email: user.email},accessToken,refreshToken};}// 刷新token请求@Post('api/refresh')refresh(@Body() body: any) {try {console.log('refresh token');console.log(body.token);const data = this.jwtService.verify(body.token);const user = users.find(item => item.username === data.username);const accessToken = this.jwtService.sign({username: user.username,email: user.email}, {expiresIn: '0.5h'});const refreshToken = this.jwtService.sign({username: user.username}, {expiresIn: '7d'})return {accessToken,refreshToken};} catch (e) {throw new UnauthorizedException('token 失效,请重新登录');}}// 验证token获取用户信息@Get('api/getinfo')getinfo(@Req() req: Request) {const authorization = req.headers['authorization'];if (!authorization) {throw new UnauthorizedException('用户未登录');}try {const token = authorization.split(' ')[1];const data = this.jwtService.verify(token);return {userInfo: {username: data.username,email: data.email}};} catch (e) {throw new UnauthorizedException('token 失效,请重新登录');}}
创建user.dto.ts
export class UserDto {username: string;password: string;
}
AppController添加模拟数据
const users = [{ username: 'test', password: 'success', email: 'abc@163.com' }
]
前端Hbuilder创建VUE3项目
安装axios
pnpm i axios
src目录下创建以下两个文件
utils/request.js
//request.js
import axios from "axios";
import { resolveResError } from "./helpers";const server = axios.create({baseURL: "/api",timeout: 1000 * 10,headers: {"Content-type": "application/json"}
})
var requesting = false
/*请求拦截器*/
function reqResolve(config) {let accessToken = localStorage.getItem('access_token')if (accessToken) {config.headers.Authorization = 'Bearer ' + accessToken}return config
}function reqReject(error) {return Promise.reject(error)
}const SUCCESS_CODES = [0, 200, 201, 202, 203, 204, 205]
/*响应拦截器*/
function resResolve(response) {const { data, status, config, statusText, headers } = responseif (headers['content-type']?.includes('json')) {//获取状态码const code = data?.code ?? status//检查是否保持if (SUCCESS_CODES.includes(code)) {return Promise.resolve(data)}// 根据code处理对应的操作,并返回处理后的messageconst message = resolveResError(code, data?.message ?? statusText)//需要错误提醒(是否不需要提示)!config?.noNeedTip && message && window.$message?.error(message)return Promise.reject({ code, message, error: data ?? response })}return Promise.resolve(data ?? response)
}async function resReject(error) {if (!error || !error.response) {const code = error?.code/** 根据code处理对应的操作,并返回处理后的message */const message = resolveResError(code, error.message)window.$message?.error(message)return Promise.reject({ code, message, error })}const { data, status, config } = error.responseconst code = data?.code ?? statusconst message = resolveResError(code, data?.message ?? error.message)let originalRequest = error.config;let refreshToken = localStorage.getItem('refresh_token');switch (code) {case 400:if (message == '用户不存在') {return Promise.reject({ code, message, error })}break;case 401:if (refreshToken && !originalRequest._retry && !requesting) {originalRequest._retry = true;requesting = truetry {// 使用refresh token尝试获取新的tokens/refreshToken = localStorage.getItem('refresh_token');console.log("刷新refreshToken");console.log(refreshToken);const refreshResponse = await axios.post('/api/refresh', {"token": refreshToken}).then((res) => {return res;}).catch((e) => {// 刷新token失效会跳转下面的catchreturn e;})if (refreshResponse?.data.accessToken) {localStorage.setItem('access_token', refreshResponse.data.accessToken);localStorage.setItem('refresh_token', refreshResponse.data.refreshToken);// 在原始请求中添加新的access token,并标记为重试请求originalRequest.headers.Authorization = `Bearer ${refreshResponse.accessToken}`;requesting = false// 重新发起请求return await server(originalRequest);}} catch (refreshError) {// 若刷新token失败,清除存储的tokens并通知用户重新登录localStorage.removeItem('access_token');localStorage.removeItem('refresh_token');alert('登录过期,请重新登录');console.log("刷新token失败");requesting = false}} else {// 若无refresh token,直接提示用户重新登录localStorage.removeItem('access_token');localStorage.removeItem('refresh_token');console.log("无刷新token");alert('登录过期,请重新登录');}break;case 403:console.log("没有权限");break;}/** 需要错误提醒 */!config?.noNeedTip && message && window.$message?.error(message)return Promise.reject({ code, message, error: error.response?.data || error.response })
}
server.interceptors.request.use(reqResolve, reqReject)
server.interceptors.response.use(resResolve, resReject)export default server
unitls/helper.js
export function resolveResError(code, message) {switch (code) {case 401:message = '登录已过期,是否重新登录'breakcase 11007:case 11008:message = '退出登录'breakcase 403:message = '请求被拒绝'breakcase 404:message = '请求资源或接口不存在'breakcase 500:message = '服务器发生异常'breakdefault:message = message ?? `【${code}】: 未知异常!`break}return message
}
根目录下添加.env配置环境
VITE_TITLE = '待煎的闲鱼'
# 是否使用Hash路由
VITE_USE_HASH = 'true'# 资源公共路径,需要以 /开头和结尾
VITE_PUBLIC_PATH = '/'# 代理配置-target 本地服务
VITE_PROXY_TARGET = 'http://localhost:3000'
根目录下创建vite.config.js配置代理
import path from 'path'
import { defineConfig, loadEnv } from 'vite'
import Vue from '@vitejs/plugin-vue'// https://vitejs.dev/config/
export default defineConfig(({ command, mode }) => {const isBuild = command === 'build'const viteEnv = loadEnv(mode, process.cwd())const { VITE_TITLE, VITE_PUBLIC_PATH, VITE_PROXY_TARGET } = viteEnvreturn {plugins: [Vue()],base: VITE_PUBLIC_PATH || '/',resolve: {alias: {'@': path.resolve(process.cwd(), 'src'),'~': path.resolve(process.cwd()),},},server: {port: 3200, // 设置服务启动端口号// open: true, // 设置服务启动时是否自动打开浏览器cors: true, // 允许跨域// 设置代理,根据我们项目实际情况配置proxy: {'/api': { //api是自行设置的请求前缀,按照这个来匹配请求,有这个字段的请求,就会进到代理来target: "http://localhost:3000", //是自己需要调的接口的前缀域名ws: false,changeOrigin: true},}}}
})
相关文章:

Vue:双token无感刷新
文章目录 初次授权与发放Token:Access Token的作用:Refresh Token的作用:无感刷新:安全机制:后端创建nest项目AppController 添加login、refresh、getinfo接口创建user.dto.tsAppController添加模拟数据 前端Hbuilder创…...
实现一个作用域插槽的场景
vue项目中,插槽slot有三种分别是:默认插槽、具名插槽、作用域插槽。默认插槽和具名插槽在平时的开发中用的比较多,作用域插槽用的相对较少,以前我对作用域插槽不是很理解,现在理解了一下。下面通过代码来实现一个作用域…...

Qt QPainter的使用方法
重点: 1.QPainter在QWidget窗口的paintEvent中使用。 2.QPainter通常涉及到设置画笔、设置画刷、绘图(QPen、QBrush、drawxx)三个流程。 class Widget : public QWidget {Q_OBJECTprotected:void paintEvent(QPaintEvent *event) Q_DEC…...

低代码:数智化助力新农业发展
随着科技的飞速发展和数字化转型的深入推进,低代码开发平台正逐渐成为软件开发的热门话题。尤其在农业领域,低代码技术为传统农业注入了新的活力,助力新农业实现高效、智能的发展。 低代码开发平台的概念与特点 随着科技的飞速发展࿰…...

3d模型怎么镜像?3d模型镜像的步骤---模大狮模型网
在3D建模软件中,对3D模型进行镜像操作通常是指沿着某个轴线(如X、Y、Z轴)进行镜像翻转,使模型在该轴线的一侧产生对称的镜像效果。以下是在常见的3D建模软件中对3D模型进行镜像的一般步骤: 3d模型镜像步骤: 选择模型:…...

笔记本hp6930p安装Android-x86补记
在上一篇日记中(笔记本hp6930p安装Android-x86避坑日记-CSDN博客)提到hp6930p安装Android-x86-9.0,无法正常启动,本文对此再做尝试,原因是:Android-x86-9.0不支持无线网卡,需要在BIOS中关闭WLAN…...

为什么MySQL中多表联查效率低,连接查询实现的原理是什么?
MySQL中多表联查效率低的原因主要涉及到以下几个方面: 数据量大: 当多个表通过连接查询时,如果这些表的数据量很大,那么查询就需要处理更多的数据,这自然会降低查询效率。 连接操作复杂性: 连接查询需要对参与连接的每个表中的数…...

从下一代车规MCU厘清存储器的发展(2)
目录 1.概述 2.MCU大厂的选择 2.1 瑞萨自研STT-MRAM 2.2 ST专注PCM 2.3 英飞凌和台积电联手RRAM 2.4 NXP如何计划eNVM 3.小结 1.概述 上篇文章,我们简述了当前主流的存储器技术,现在我们来讲讲各大MCU大厂的技术选择 2.MCU大厂的选择 瑞萨日…...
Redis(理论版)
Redis 1.Redis是什么 Redis其实就是一个数据库,它是一个文档型数据库(非关系型数据库),而mysql是一个关系型数据库。它是一个开源的、基于内存的高性能键值存储数据库,支持多种数据结构,广泛用于缓存、消息队列、应用…...

【NR 定位】3GPP NR Positioning 5G定位标准解读(四)
目录 前言 6 Signalling protocols and interfaces 6.1 支持定位操作的网络接口 6.1.1 通用LCS控制平面架构 6.1.2 NR-Uu接口 6.1.3 LTE-Uu接口 6.1.4 NG-C接口 6.1.5 NL1接口 6.1.6 F1接口 6.1.7 NR PC5接口 6.2 终端协议 6.2.1 LTE定位协议(LPP&#x…...

Docker容器化解决方案
什么是Docker? Docker是一个构建在LXC之上,基于进程容器的轻量级VM解决方案,实现了一种应用程序级别的资源隔离及配额。Docker起源于PaaS提供商dotCloud 基于go语言开发,遵从Apache2.0开源协议。 Docker 自开源后受到广泛的关注和…...

Docker安装+基础命令
一、检测、配置安装环境 (1)查看linux版本,是否符合>centos 7 (2)查看网络是否通畅 (3)安装gcc,gcc-c编译器 (4)安装device-mapper-persistent-data和lvm2…...

构建高性能Linux Virtual Server(LVS)集群
目录 引言 一、集群的基本理论 (一)什么是集群 (二)集群的分类 (三)LB Cluster 负载均衡集群 1.按实现方式划分 2.按协议层划分 (四)HA 高可用集群实现 二、LVS简介 &…...

Linux:线程的概念
个人主页 : 个人主页 个人专栏 : 《数据结构》 《C语言》《C》《Linux》 文章目录 前言一、线程的概念线程代码的简单示例 总结 前言 本文是对于线程概念的知识总结 一、线程的概念 在课本上,线程是比进程更轻量级的一种指向流 或 线程是在…...

如何在jupyter notebook 中下载第三方库
在anconda 中找到: Anaconda Prompt 进入页面后的样式: 在黑色框中输入: 下载第三方库的命令 第三方库: 三种输入方式 标准保证正确 pip instsall 包名 -i 镜像源地址 pip install pip 是 Python 包管理工具,…...

Linux下du命令和df命令的使用
du命令作用是估计文件系统的磁盘已使用量,常用于查看文件或目录所占磁盘容量。df命令是统计磁盘使用情况,可以用来查看磁盘已被使用多少空间和还剩余多少空间。du命令语法du [选项] [文件或目录名称]参数:-a:--all, 列…...
AIGC笔记--条件自回归Transformer的搭建
1--概述 1. 自回归 TransFormer 规定Token只能看到自身及前面的Token,因此需生成一个符合规定的Attention Mask;(代码提供了两种方式自回归Attention Mask的定义方式); 2. 使用Cross Attention实现条件模态和输入模态之…...

数据结构->链表分类与oj(题),带你提升代码好感
✅作者简介:大家好,我是橘橙黄又青,一个想要与大家共同进步的男人😉😉 🍎个人主页:橘橙黄又青-CSDN博客 1.🍎链表的分类 前面我们学过顺序表,顺序表问题: …...
unity-unity2d基础操作笔记(三)0.5.000
目标是:牢记以下137条操作,越级上升到中级阶段 unity-unity2d基础操作笔记(三) 一百零一、如何操作一个游戏物体由多个部分组成的动画一百零二、如何使用rigidbody 2d进行物体移动一百零三、获取游戏物体身上的组件方法一百零四、代码控制物体朝向一百零五、不使用插件,纯…...
【精华】AIGC启元2024
文章目录 AIGC 前沿(1) Gemini 1.5 Pro(2) Sora(3) EMO(4) Playground v2.5(5) VSP-LLM(6) Ideogram.ai(7) LTX studio AIGC 前沿 (1) Gemini 1.5 Pro 2024.02.16 谷歌新一代多模态大模型Gemini 1.5 Pro,在性能上超越OpenAI的GPT-4 Turbo,堪称业界最强…...

Appium+python自动化(十六)- ADB命令
简介 Android 调试桥(adb)是多种用途的工具,该工具可以帮助你你管理设备或模拟器 的状态。 adb ( Android Debug Bridge)是一个通用命令行工具,其允许您与模拟器实例或连接的 Android 设备进行通信。它可为各种设备操作提供便利,如安装和调试…...

【入坑系列】TiDB 强制索引在不同库下不生效问题
文章目录 背景SQL 优化情况线上SQL运行情况分析怀疑1:执行计划绑定问题?尝试:SHOW WARNINGS 查看警告探索 TiDB 的 USE_INDEX 写法Hint 不生效问题排查解决参考背景 项目中使用 TiDB 数据库,并对 SQL 进行优化了,添加了强制索引。 UAT 环境已经生效,但 PROD 环境强制索…...
Cesium1.95中高性能加载1500个点
一、基本方式: 图标使用.png比.svg性能要好 <template><div id"cesiumContainer"></div><div class"toolbar"><button id"resetButton">重新生成点</button><span id"countDisplay&qu…...

HTML 列表、表格、表单
1 列表标签 作用:布局内容排列整齐的区域 列表分类:无序列表、有序列表、定义列表。 例如: 1.1 无序列表 标签:ul 嵌套 li,ul是无序列表,li是列表条目。 注意事项: ul 标签里面只能包裹 li…...
OkHttp 中实现断点续传 demo
在 OkHttp 中实现断点续传主要通过以下步骤完成,核心是利用 HTTP 协议的 Range 请求头指定下载范围: 实现原理 Range 请求头:向服务器请求文件的特定字节范围(如 Range: bytes1024-) 本地文件记录:保存已…...

P3 QT项目----记事本(3.8)
3.8 记事本项目总结 项目源码 1.main.cpp #include "widget.h" #include <QApplication> int main(int argc, char *argv[]) {QApplication a(argc, argv);Widget w;w.show();return a.exec(); } 2.widget.cpp #include "widget.h" #include &q…...
土地利用/土地覆盖遥感解译与基于CLUE模型未来变化情景预测;从基础到高级,涵盖ArcGIS数据处理、ENVI遥感解译与CLUE模型情景模拟等
🔍 土地利用/土地覆盖数据是生态、环境和气象等诸多领域模型的关键输入参数。通过遥感影像解译技术,可以精准获取历史或当前任何一个区域的土地利用/土地覆盖情况。这些数据不仅能够用于评估区域生态环境的变化趋势,还能有效评价重大生态工程…...
【HTML-16】深入理解HTML中的块元素与行内元素
HTML元素根据其显示特性可以分为两大类:块元素(Block-level Elements)和行内元素(Inline Elements)。理解这两者的区别对于构建良好的网页布局至关重要。本文将全面解析这两种元素的特性、区别以及实际应用场景。 1. 块元素(Block-level Elements) 1.1 基本特性 …...
精益数据分析(97/126):邮件营销与用户参与度的关键指标优化指南
精益数据分析(97/126):邮件营销与用户参与度的关键指标优化指南 在数字化营销时代,邮件列表效度、用户参与度和网站性能等指标往往决定着创业公司的增长成败。今天,我们将深入解析邮件打开率、网站可用性、页面参与时…...

分布式增量爬虫实现方案
之前我们在讨论的是分布式爬虫如何实现增量爬取。增量爬虫的目标是只爬取新产生或发生变化的页面,避免重复抓取,以节省资源和时间。 在分布式环境下,增量爬虫的实现需要考虑多个爬虫节点之间的协调和去重。 另一种思路:将增量判…...