记录-使用双token实现无感刷新,前后端详细代码
这里给大家分享我在网上总结出来的一些知识,希望对大家有所帮助
前言
近期写的一个项目使用双token实现无感刷新。最后做了一些总结,本文详细介绍了实现流程,前后端详细代码。前端使用了Vue3+Vite,主要是axios封装,服务端使用了koa2做了一个简单的服务器模拟。
一、token 登录鉴权
jwt:JSON Web Token。是一种认证协议,一般用来校验请求的身份信息和身份权限。 由三部分组成:Header、Hayload、Signature
header:也就是头部信息,是描述这个 token 的基本信息,json 格式
{
"alg": "HS256", // 表示签名的算法,默认是 HMAC SHA256(写成 HS256)
"type": "JWT" // 表示Token的类型,JWT 令牌统一写为JWT
}
{
"iss": "a.com", // 签发人
"exp": "1d", // expiration time 过期时间
"sub": "test", // 主题
"aud": "", // 受众
"nbf": "", // Not Before 生效时间
"iat": "", // Issued At 签发时间
"jti": "", // JWT ID 编号
// 可以定义私有字段
"name": "",
"admin": ""
}
Signature 签名 是对前两部分的签名,防止数据被篡改。 需要指定一个密钥。这个密钥只有服务器才知道,不能泄露。使用 Header 里面指定的签名算法,按照公式产生签名。
算出签名后,把 Header、Payload、Signature 三个部分拼成的一个字符串,每个部分之间用 . 分隔。这样就生成了一个 token
二、何为双 token
accessToken:用户获取数据权限refreshToken:用来获取新的accessToken
双 token 验证机制,其中 accessToken 过期时间较短,refreshToken 过期时间较长。当 accessToken 过期后,使用 refreshToken 去请求新的 token。
双 token 验证流程
- 用户登录向服务端发送账号密码,登录失败返回客户端重新登录。登录成功服务端生成 accessToken 和 refreshToken,返回生成的 token 给客户端。
- 在请求拦截器中,请求头中携带 accessToken 请求数据,服务端验证 accessToken 是否过期。token 有效继续请求数据,token 失效返回失效信息到客户端。
- 客户端收到服务端发送的请求信息,在二次封装的 axios 的响应拦截器中判断是否有 accessToken 失效的信息,没有返回响应的数据。有失效的信息,就携带 refreshToken 请求新的 accessToken。
- 服务端验证 refreshToken 是否有效。有效,重新生成 token, 返回新的 token 和提示信息到客户端,无效,返回无效信息给客户端。
- 客户端响应拦截器判断响应信息是否有 refreshToken 有效无效。无效,退出当前登录。有效,重新存储新的 token,继续请求上一次请求的数据。
注意事项
- 短token失效,服务端拒绝请求,返回token失效信息,前端请求到新的短token如何再次请求数据,达到无感刷新的效果。
- 服务端白名单,成功登录前是还没有请求到token的,那么如果服务端拦截请求,就无法登录。定制白名单,让登录无需进行token验证。
三、服务端代码
1. 搭建koa2服务器
全局安装koa脚手架
npm install koa-generator -g
koa2 server
cd server 进入到项目安装jwt
npm i jsonwebtoken
npm i koa-cors
const cors=require('koa-cors')
...
app.use(cors())
2. 双token
新建utils/token.js
const jwt=require('jsonwebtoken')
const secret='2023F_Ycb/wp_sd' // 密钥
/*
expiresIn:5 过期时间,时间单位是秒
也可以这么写 expiresIn:1d 代表一天
1h 代表一小时
*/
// 本次是为了测试,所以设置时间 短token5秒 长token15秒
const accessTokenTime=5
const refreshTokenTime=15
// 生成accessToken
const setAccessToken=(payload={})=>{ // payload 携带用户信息
return jwt.sign(payload,secret,{expireIn:accessTokenTime})
}
//生成refreshToken
const setRefreshToken=(payload={})=>{
return jwt.sign(payload,secret,{expireIn:refreshTokenTime})
}
module.exports={
secret,
setAccessToken,
setRefreshToken
}
3. 路由
直接使用脚手架创建的项目已经在app.js使用了路由中间件 在router/index.js 创建接口
const router = require('koa-router')()
const jwt = require('jsonwebtoken')
const { getAccesstoken, getRefreshtoken, secret }=require('../utils/token')
/*登录接口*/
router.get('/login',()=>{
let code,msg,data=null
code=2000
msg='登录成功,获取到token'
data={
accessToken:getAccessToken(),
refreshToken:getReferToken()
}
ctx.body={
code,
msg,
data
}
})
/*用于测试的获取数据接口*/
router.get('/getTestData',(ctx)=>{
let code,msg,data=null
code=2000
msg='获取数据成功'
ctx.body={
code,
msg,
data
}
})
/*验证长token是否有效,刷新短token
这里要注意,在刷新短token的时候回也返回新的长token,延续长token,
这样活跃用户在持续操作过程中不会被迫退出登录。长时间无操作的非活
跃用户长token过期重新登录
*/
router.get('/refresh',(ctx)=>{
let code,msg,data=null
//获取请求头中携带的长token
let r_tk=ctx.request.headers['pass']
//解析token 参数 token 密钥 回调函数返回信息
jwt.verify(r_tk,secret,(error)=>{
if(error){
code=4006,
msg='长token无效,请重新登录'
} else{
code=2000,
msg='长token有效,返回新的token',
data={
accessToken:getAccessToken(),
refreshToken:getReferToken()
}
}
})
})
4. 应用中间件
utils/auth.js
const { secret } = require('./token')
const jwt = require('jsonwebtoken')
/*白名单,登录、刷新短token不受限制,也就不用token验证*/
const whiteList=['/login','/refresh']
const isWhiteList=(url,whiteList)=>{
return whiteList.find(item => item === url) ? true : false
}
/*中间件
验证短token是否有效
*/
const cuth = async (ctx,next)=>{
let code, msg, data = null
let url = ctx.path
if(isWhiteList(url,whiteList)){
// 执行下一步
return await next()
} else {
// 获取请求头携带的短token
const a_tk=ctx.request.headers['authorization']
if(!a_tk){
code=4003
msg='accessToken无效,无权限'
ctx.body={
code,
msg,
data
}
} else{
// 解析token
await jwt.verify(a_tk,secret.(error)=>{
if(error)=>{
code=4003
msg='accessToken无效,无权限'
ctx.body={
code,
msg,
datta
}
} else {
// token有效
return await next()
}
})
}
}
}
module.exports=auth
const auth=requier(./utils/auth)
···
app.use(auth)
其实如果只是做一个简单的双token验证,很多中间件是没必要的,比如解析静态资源。不过为了节省时间,方便就直接使用了koa2脚手架。
最终目录结构:

四、前端代码
1. Vue3+Vite框架
前端使用了Vue3+Vite的框架,看个人使用习惯。
npm init vite@latest client_side
npm i axios
2. 定义使用到的常量
config/constants.js
export const ACCESS_TOKEN = 'a_tk' // 短token字段
export const REFRESH_TOKEN = 'r_tk' // 短token字段
export const AUTH = 'Authorization' // header头部 携带短token
export const PASS = 'pass' // header头部 携带长token
3. 存储、调用过期请求
关键点:把携带过期token的请求,利用Promise存在数组中,保持pending状态,也就是不调用resolve()。当获取到新的token,再重新请求。 utils/refresh.js
export {REFRESH_TOKEN,PASS} from '../config/constants.js'
import { getRefreshToken, removeRefreshToken, setAccessToken, setRefreshToken} from '../config/storage'
let subsequent=[]
let flag=false // 设置开关,保证一次只能请求一次短token,防止客户多此操作,多次请求
/*把过期请求添加在数组中*/
export const addRequest = (request) => {
subscribes.push(request)
}
/*调用过期请求*/
export const retryRequest = () => {
console.log('重新请求上次中断的数据');
subscribes.forEach(request => request())
subscribes = []
}
/*短token过期,携带token去重新请求token*/
export const refreshToken=()=>{
if(!flag){
flag = true;
let r_tk = getRefershToken() // 获取长token
if(r_tk){
server.get('/refresh',Object.assign({},{
headers:{[PASS]=r_tk}
})).then((res)=>{
//长token失效,退出登录
if(res.code===4006){
flag = false
removeRefershToken(REFRESH_TOKEN)
} else if(res.code===2000){
// 存储新的token
setAccessToken(res.data.accessToken)
setRefreshToken(res.data.refreshToken)
flag = false
// 重新请求数据
retryRequest()
}
})
}
}
}
4. 封装axios
utlis/server.js
import axios from "axios";
import * as storage from "../config/storage"
import * as constants from '../config/constants'
import { addRequest, refreshToken } from "./refresh"; const server = axios.create({
baseURL: 'http://localhost:3004', // 你的服务器
timeout: 1000 * 10,
headers: {
"Content-type": "application/json"
}
}) /*请求拦截器*/
server.interceptors.request.use(config => {
// 获取短token,携带到请求头,服务端校验
let aToken = storage.getAccessToken(constants.ACCESS_TOKEN)
config.headers[constants.AUTH] = aToken
return config
}) /*响应拦截器*/
server.interceptors.response.use(
async response => {
// 获取到配置和后端响应的数据
let { config, data } = response
console.log('响应提示信息:', data.msg);
return new Promise((resolve, reject) => {
// 短token失效
if (data.code === 4003) {
// 移除失效的短token
storage.removeAccessToken(constants.ACCESS_TOKEN)
// 把过期请求存储起来,用于请求到新的短token,再次请求,达到无感刷新
addRequest(() => resolve(server(config)))
// 携带长token去请求新的token
refreshToken()
} else {
// 有效返回相应的数据
resolve(data)
} }) },
error => {
return Promise.reject(error)
}
)
5. 复用封装
import * as constants from "./constants" // 存储短token
export const setAccessToken = (token) => localStorage.setItem(constanst.ACCESS_TOKEN, token)
// 存储长token
export const setRefershToken = (token) => localStorage.setItem(constants.REFRESH_TOKEN, token)
// 获取短token
export const getAccessToken = () => localStorage.getItem(constants.ACCESS_TOKEN)
// 获取长token
export const getRefershToken = () => localStorage.getItem(constants.REFRESH_TOKEN)
// 删除短token
export const removeAccessToken = () => localStorage.removeItem(constants.ACCESS_TOKEN)
// 删除长token
export const removeRefershToken = () => localStorage.removeItem(constants.REFRESH_TOKEN)
6. 接口封装
apis/index.js
import server from "../utils/server";
/*登录*/
export const login = () => {
return server({
url: '/login',
method: 'get'
})
}
/*请求数据*/
export const getData = () => {
return server({
url: '/getList',
method: 'get'
})
}


本文转载于:
https://juejin.cn/post/7224764099187736634
如果对您有所帮助,欢迎您点个关注,我会定时更新技术文档,大家一起讨论学习,一起进步。

记录-使用双token实现无感刷新,前后端详细代码的更多相关文章
- 无感刷新 Token
什么是JWT JWT是全称是JSON WEB TOKEN,是一个开放标准,用于将各方数据信息作为JSON格式进行对象传递,可以对数据进行可选的数字加密,可使用RSA或ECDSA进行公钥/私钥签名. 使 ...
- Spring Cloud实战 | 最八篇:Spring Cloud +Spring Security OAuth2+ Axios前后端分离模式下无感刷新实现JWT续期
一. 前言 记得上一篇Spring Cloud的文章关于如何使JWT失效进行了理论结合代码实践的说明,想当然的以为那篇会是基于Spring Cloud统一认证架构系列的最终篇.但关于JWT另外还有一个 ...
- axios实现无感刷新
前言 最近在做需求的时候,涉及到登录token,产品提出一个问题:能不能让token过期时间长一点,我频繁的要去登录. 前端:后端,你能不能把token 过期时间设置的长一点. 后端:可以,但是那样做 ...
- Cookie、Session、Token那点事儿和前后端分离之JWT用户认证
(两篇文章转自:https://www.jianshu.com/p/bd1be47a16c1:https://www.jianshu.com/p/180a870a308a) 什么是Cookie? Co ...
- 记录一次用宝塔部署微信小程序Node.js后端接口代码的详细过程
一直忙着写毕设,上一次写博客还是元旦,大半年过去了.... 后面会不断分享各种新项目的源码与技术.欢迎关注一起学习哈! 记录一次部署微信小程序Node.js后端接口代码的详细过程,使用宝塔来部署. 我 ...
- 基于OAuth2.0的token无感知刷新
目前手头的vue项目关于权限一块有一个需求,其实架构师很早就要求我做了,但是由于这个紧急程度不是很高,最近临近项目上线,我才想起,于是赶紧补上这个功能.这个项目是基于OAuth2.0认证,需要在每个请 ...
- 前后端分离——token超时刷新策略
前言 记录一下前后端分离下————token超时刷新策略! 需求场景 昨天发了一篇记录 前后端分离应用——用户信息传递 中介绍了token认证机制,跟几位群友讨论了下,有些同学有这么一个疑惑:toke ...
- .Net中使用无闪刷新控件时提示框不显示
今天做提示框的时候一直不显示,让我郁闷好久,晚上吃饭的时候问了同事一下,他给了一个思路, 他说可能是因为由于页面中的无闪刷新导致的结果:百度了一下真找到了解决方法 在页面中存在无闪刷新控件的时候提示框 ...
- WPF MVVM模式下的无阻塞刷新探讨
很多时候我们需要做一个工作,在一个方法体里面,读取大数据绑定到UI界面,由于长时间的读取,读取独占了线程域,导致界面一直处于假死状态.例如,当应用程序开始读取Web资源时,读取的时效是由网络链路的速度 ...
- ArcEngine 图层无闪烁刷新
使用AE的同行经常会遇到这样的问题,图层刷新.目前常用的有以下几种方法: 1.完全刷新 MapControl.Refresh(); 2.局部刷新 MapControl.Refresh(esriView ...
随机推荐
- 鹏程杯子2023 pwn
主要就是修改stdin的最后几位,使他变为write,然后泄露libc,为所欲为即可. 本人是卡在不知道stdin那里可以修改. 然后使用一下jmp qword rbp这个gadget 0x400a9 ...
- NC23413 小A买彩票
题目链接 题目 题目描述 小A最近开始沉迷买彩票,并且希望能够通过买彩票发家致富.已知购买一张彩票需要3元,而彩票中奖的金额分别为1,2,3,4元,并且比较独特的是这个彩票中奖的各种金额都是等可能的. ...
- RFID EPC Class1 Gen2电子标签笔记
RFID EPC Class1 Gen2 符合EPC Class1 Gen2(简称G2)协议V109版的电子标签(Tag)和读写器(Reader)应该具有下述的特性 标签存储器分区 Tag memor ...
- 【Map】【List】【数组】获得两个数组的交集
给定两个数组,编写一个函数来计算它们的交集. 示例 1: 输入: nums1 = [1,2,2,1], nums2 = [2,2] 输出: [2,2] 示例 2: 输入: nums1 = [1,2,2 ...
- 代码+案例,实战解析BeautifulSoup4
本文分享自华为云社区<从HTML到实战:深入解析BeautifulSoup4的爬虫奇妙世界>,作者:柠檬味拥抱. 网络上的信息浩如烟海,而爬虫技术正是帮助我们从中获取有用信息的重要工具.在 ...
- SetWindowHookEx的复习
#include <Windows.h> #include <iostream> using namespace std; HHOOK keyboardHook; LRESUL ...
- Direct2D 画刷篇
微软文档:Brushes overview 本篇通过官方文档学习,整理出来的demo,初始样本请先创建一个普通的desktop app. // Test_Direct2D_Brush.cpp : De ...
- win32-使用GDI+缩放图像
滑动鼠标滚轮可以改变图像大小 #include <windows.h> #include <tchar.h> #include <Urlmon.h> // URLD ...
- 麒麟系统开发笔记(六):安装QtCreator开发IDE中的中文输入环境Fcitx输入法
前言 中文输入法,QtCreator中无法输入中文也是ubuntu中一个常规问题,在麒麟系统中也此问题,要解决此问题,主要是安装和使用Fcitx输入法. 本文章最终结果是失败的,但是读者的系统未 ...
- 硬件开发笔记(二):硬件开发基本流程,制作一个USB转RS232的模块(一):开发基本过程和元器件选型
前言 做个usb转串口,同时兼容ttl,讲述硬件模块基础的开发流程,本篇描述了全流程过程,然后选型了合适的元器件. 基本流程 以下是笔者个人从事过相关硬件研发,总结出来的流程,仅代表个人意 ...
