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

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…...

低代码:数智化助力新农业发展

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

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,堪称业界最强…...

js对象解构语法

对象解构语法是一种 JavaScript 的语法特性,用于从对象中提取属性,并将这些属性值赋值给变量。 基本语法 const { property1, property2 } object;object 是要解构的对象。property1 和 property2 是对象中的属性名,用花括号 {} 包裹起来表…...

flowable使用taskService.addComment新增评论需要full_msg字段进行读取

背景 在构建创业项目JeecgFlow过程中,在调用taskService.addComment接口出现了异常。就是数据存储的Message信息出现了截取,也就是存储不完整。 效果如下. flowable版本6.7.2 问题排查 接口详解及问题代码 //新增评论的接口说明 Comment addComment(…...

java常用技术栈,java面试带答案

前言 我们从一个问题引入今天的主题。 在日常业务开发中,我们可能经常听到 DBA 对我们说“不要”(注意:不是禁止)使用 join,那么为什么 DBA 对 join 这么抵触呢?是 join 本身有问题,还是我们使…...

刷题第11天

代码随想录刷题第11天 | 二叉树前中后序遍历 前序遍历 /*** Definition for a binary tree node.* struct TreeNode {* int val;* TreeNode *left;* TreeNode *right;* TreeNode() : val(0), left(nullptr), right(nullptr) {}* TreeNode(int x) : val(x…...

QML中动态增加表格数据

1.QML中的表格实现 import QtQuick 2.15 import QtQuick.Window 2.15import QtQuick.Controls 2.0 import Qt.labs.qmlmodels 1.0 import QtQuick.Layouts 1.15Window {width: 640height: 480visible: truetitle: qsTr("Hello World")TableModel{id:table_modelTabl…...

OBS插件开发(二)推流实时曲线

不发视频了,截个图算了,嫌麻烦 1,自定义QWidget图表绘制 ,动态更新 2,OBS直播帧率,码率监控 3,主要用于前端推流状况可视化,异常报警,及时性,无人值守直播...

Linux编程3.3 进程-进程的终止

1、正常终止 从main函数返回调用exit(标准C库函数)调用_exti或_Exit(系统调用)最后一个线程从其启动例程返回最后一个线程调用 pthread exit 2、异常终止 调用abort接受到一个信号并终止最后一个线程对取消请求做处理响应 3、进程返回 通常程序运行…...

排序(3)——直接选择排序

目录 直接选择排序 基本思想 整体思路(升序) 单趟 多趟 代码实现 特性总结 直接选择排序 基本思想 每一次从待排序的数据元素中选出最小(或最大)的一个元素,存放在序列的起始位置,直到全部待排序的…...

[LeetBook]【学习日记】数组内重组

题目:训练计划 I 训练计划 I 教练使用整数数组 actions 记录一系列核心肌群训练项目编号。为增强训练趣味性,需要将所有奇数编号训练项目调整至偶数编号训练项目之前。请将调整后的训练项目编号以数组形式返回。 示例 1: 输入:act…...

【Linux】磁盘情况、挂载,df -h无法看到的卷

文章目录 解决挂载、解决挂载完重启就消失1、查看linux下的硬盘挂载的空间、使用空间2、查看没有挂载的硬盘是否检测在系统中3、挂载 (挂载完,要在/etc/fstab 下面配置挂载信息 要不然重启挂载就消失了) 解决挂载、解决挂载完重启就消失 linu…...