Vue3+Vite使用雙token實(shí)現(xiàn)無感刷新
前言
近期寫的一個(gè)項(xiàng)目使用雙token實(shí)現(xiàn)無感刷新。最后做了一些總結(jié),本文詳細(xì)介紹了實(shí)現(xiàn)流程,前后端詳細(xì)代碼。前端使用了Vue3+Vite,主要是axios封裝,服務(wù)端使用了koa2做了一個(gè)簡單的服務(wù)器模擬。
一、token 登錄鑒權(quán)
jwt:JSON Web Token。是一種認(rèn)證協(xié)議,一般用來校驗(yàn)請(qǐng)求的身份信息和身份權(quán)限。 由三部分組成:Header、Hayload、Signature
header:也就是頭部信息,是描述這個(gè) token 的基本信息,json 格式
{
"alg": "HS256", // 表示簽名的算法,默認(rèn)是 HMAC SHA256(寫成 HS256)
"type": "JWT" // 表示Token的類型,JWT 令牌統(tǒng)一寫為JWT
}
payload:載荷,也是一個(gè) JSON 對(duì)象,用來存放實(shí)際需要傳遞的數(shù)據(jù)。不建議存放敏感信息,比如密碼。
{
"iss": "a.com", // 簽發(fā)人
"exp": "1d", // expiration time 過期時(shí)間
"sub": "test", // 主題
"aud": "", // 受眾
"nbf": "", // Not Before 生效時(shí)間
"iat": "", // Issued At 簽發(fā)時(shí)間
"jti": "", // JWT ID 編號(hào)
// 可以定義私有字段
"name": "",
"admin": ""
}
Signature 簽名 是對(duì)前兩部分的簽名,防止數(shù)據(jù)被篡改。 需要指定一個(gè)密鑰。這個(gè)密鑰只有服務(wù)器才知道,不能泄露。使用 Header 里面指定的簽名算法,按照公式產(chǎn)生簽名。
算出簽名后,把 Header、Payload、Signature 三個(gè)部分拼成的一個(gè)字符串,每個(gè)部分之間用 . 分隔。這樣就生成了一個(gè) token
二、何為雙 token
accessToken:用戶獲取數(shù)據(jù)權(quán)限refreshToken:用來獲取新的accessToken
雙 token 驗(yàn)證機(jī)制,其中 accessToken 過期時(shí)間較短,refreshToken 過期時(shí)間較長。當(dāng) accessToken 過期后,使用 refreshToken 去請(qǐng)求新的 token。
雙 token 驗(yàn)證流程
- 用戶登錄向服務(wù)端發(fā)送賬號(hào)密碼,登錄失敗返回客戶端重新登錄。登錄成功服務(wù)端生成 accessToken 和 refreshToken,返回生成的 token 給客戶端。
- 在請(qǐng)求攔截器中,請(qǐng)求頭中攜帶 accessToken 請(qǐng)求數(shù)據(jù),服務(wù)端驗(yàn)證 accessToken 是否過期。token 有效繼續(xù)請(qǐng)求數(shù)據(jù),token 失效返回失效信息到客戶端。
- 客戶端收到服務(wù)端發(fā)送的請(qǐng)求信息,在二次封裝的 axios 的響應(yīng)攔截器中判斷是否有 accessToken 失效的信息,沒有返回響應(yīng)的數(shù)據(jù)。有失效的信息,就攜帶 refreshToken 請(qǐng)求新的 accessToken。
- 服務(wù)端驗(yàn)證 refreshToken 是否有效。有效,重新生成 token, 返回新的 token 和提示信息到客戶端,無效,返回?zé)o效信息給客戶端。
- 客戶端響應(yīng)攔截器判斷響應(yīng)信息是否有 refreshToken 有效無效。無效,退出當(dāng)前登錄。有效,重新存儲(chǔ)新的 token,繼續(xù)請(qǐng)求上一次請(qǐng)求的數(shù)據(jù)。
注意事項(xiàng)
- 短token失效,服務(wù)端拒絕請(qǐng)求,返回token失效信息,前端請(qǐng)求到新的短token如何再次請(qǐng)求數(shù)據(jù),達(dá)到無感刷新的效果。
- 服務(wù)端白名單,成功登錄前是還沒有請(qǐng)求到token的,那么如果服務(wù)端攔截請(qǐng)求,就無法登錄。定制白名單,讓登錄無需進(jìn)行token驗(yàn)證。
三、服務(wù)端代碼
1. 搭建koa2服務(wù)器
全局安裝koa腳手架
npm install koa-generator -g
創(chuàng)建服務(wù)端 直接koa2+項(xiàng)目名
koa2 server
cd server 進(jìn)入到項(xiàng)目安裝jwt
npm i jsonwebtoken
為了方便直接在服務(wù)端使用koa-cors 跨域
npm i koa-cors
在app.js中引入應(yīng)用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 過期時(shí)間,時(shí)間單位是秒
也可以這么寫 expiresIn:1d 代表一天
1h 代表一小時(shí)
*/
// 本次是為了測試,所以設(shè)置時(shí)間 短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. 路由
直接使用腳手架創(chuàng)建的項(xiàng)目已經(jīng)在app.js使用了路由中間件 在router/index.js 創(chuàng)建接口
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
}
})
/*用于測試的獲取數(shù)據(jù)接口*/
router.get('/getTestData',(ctx)=>{
let code,msg,data=null
code=2000
msg='獲取數(shù)據(jù)成功'
ctx.body={
code,
msg,
data
}
})
/*驗(yàn)證長token是否有效,刷新短token
這里要注意,在刷新短token的時(shí)候回也返回新的長token,延續(xù)長token,
這樣活躍用戶在持續(xù)操作過程中不會(huì)被迫退出登錄。長時(shí)間無操作的非活
躍用戶長token過期重新登錄
*/
router.get('/refresh',(ctx)=>{
let code,msg,data=null
//獲取請(qǐng)求頭中攜帶的長token
let r_tk=ctx.request.headers['pass']
//解析token 參數(shù) token 密鑰 回調(diào)函數(shù)返回信息
jwt.verify(r_tk,secret,(error)=>{
if(error){
code=4006,
msg='長token無效,請(qǐng)重新登錄'
} else{
code=2000,
msg='長token有效,返回新的token',
data={
accessToken:getAccessToken(),
refreshToken:getReferToken()
}
}
})
})
4. 應(yīng)用中間件
utils/auth.js
const { secret } = require('./token')
const jwt = require('jsonwebtoken')
/*白名單,登錄、刷新短token不受限制,也就不用token驗(yàn)證*/
const whiteList=['/login','/refresh']
const isWhiteList=(url,whiteList)=>{
return whiteList.find(item => item === url) ? true : false
}
/*中間件
驗(yàn)證短token是否有效
*/
const cuth = async (ctx,next)=>{
let code, msg, data = null
let url = ctx.path
if(isWhiteList(url,whiteList)){
// 執(zhí)行下一步
return await next()
} else {
// 獲取請(qǐng)求頭攜帶的短token
const a_tk=ctx.request.headers['authorization']
if(!a_tk){
code=4003
msg='accessToken無效,無權(quán)限'
ctx.body={
code,
msg,
data
}
} else{
// 解析token
await jwt.verify(a_tk,secret.(error)=>{
if(error)=>{
code=4003
msg='accessToken無效,無權(quán)限'
ctx.body={
code,
msg,
datta
}
} else {
// token有效
return await next()
}
})
}
}
}
module.exports=auth
在app.js中引入應(yīng)用中間件
const auth=requier(./utils/auth) ··· app.use(auth)
其實(shí)如果只是做一個(gè)簡單的雙token驗(yàn)證,很多中間件是沒必要的,比如解析靜態(tài)資源。不過為了節(jié)省時(shí)間,方便就直接使用了koa2腳手架。
最終目錄結(jié)構(gòu):

四、前端代碼
1. Vue3+Vite框架
前端使用了Vue3+Vite的框架,看個(gè)人使用習(xí)慣。
npm init vite@latest client_side
安裝axios
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. 存儲(chǔ)、調(diào)用過期請(qǐng)求
關(guān)鍵點(diǎn):把攜帶過期token的請(qǐng)求,利用Promise存在數(shù)組中,保持pending狀態(tài),也就是不調(diào)用resolve()。當(dāng)獲取到新的token,再重新請(qǐng)求。 utils/refresh.js
export {REFRESH_TOKEN,PASS} from '../config/constants.js'
import { getRefreshToken, removeRefreshToken, setAccessToken, setRefreshToken} from '../config/storage'
let subsequent=[]
let flag=false // 設(shè)置開關(guān),保證一次只能請(qǐng)求一次短token,防止客戶多此操作,多次請(qǐng)求
/*把過期請(qǐng)求添加在數(shù)組中*/
export const addRequest = (request) => {
subscribes.push(request)
}
/*調(diào)用過期請(qǐng)求*/
export const retryRequest = () => {
console.log('重新請(qǐng)求上次中斷的數(shù)據(jù)');
subscribes.forEach(request => request())
subscribes = []
}
/*短token過期,攜帶token去重新請(qǐng)求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){
// 存儲(chǔ)新的token
setAccessToken(res.data.accessToken)
setRefreshToken(res.data.refreshToken)
flag = false
// 重新請(qǐng)求數(shù)據(jù)
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', // 你的服務(wù)器
timeout: 1000 * 10,
headers: {
"Content-type": "application/json"
}
})
/*請(qǐng)求攔截器*/
server.interceptors.request.use(config => {
// 獲取短token,攜帶到請(qǐng)求頭,服務(wù)端校驗(yàn)
let aToken = storage.getAccessToken(constants.ACCESS_TOKEN)
config.headers[constants.AUTH] = aToken
return config
})
/*響應(yīng)攔截器*/
server.interceptors.response.use(
async response => {
// 獲取到配置和后端響應(yīng)的數(shù)據(jù)
let { config, data } = response
console.log('響應(yīng)提示信息:', data.msg);
return new Promise((resolve, reject) => {
// 短token失效
if (data.code === 4003) {
// 移除失效的短token
storage.removeAccessToken(constants.ACCESS_TOKEN)
// 把過期請(qǐng)求存儲(chǔ)起來,用于請(qǐng)求到新的短token,再次請(qǐng)求,達(dá)到無感刷新
addRequest(() => resolve(server(config)))
// 攜帶長token去請(qǐng)求新的token
refreshToken()
} else {
// 有效返回相應(yīng)的數(shù)據(jù)
resolve(data)
}
})
},
error => {
return Promise.reject(error)
}
)
5. 復(fù)用封裝
import * as constants from "./constants" // 存儲(chǔ)短token export const setAccessToken = (token) => localStorage.setItem(constanst.ACCESS_TOKEN, token) // 存儲(chǔ)長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'
})
}
/*請(qǐng)求數(shù)據(jù)*/
export const getData = () => {
return server({
url: '/getList',
method: 'get'
})
}
項(xiàng)目運(yùn)行

最后的最后,運(yùn)行項(xiàng)目,查看效果 后端設(shè)置的短token5秒,長token10秒。登錄請(qǐng)求到token后,請(qǐng)求數(shù)據(jù)可以正常請(qǐng)求,五秒后再次請(qǐng)求,短token失效,這時(shí)長token有效,請(qǐng)求到新的token,refresh接口只調(diào)用了一次。長token也過期后,就需要重新登錄啦。

寫在最后
這就是一整套的前后端使用雙token機(jī)制實(shí)現(xiàn)無感刷新。token能做到的還有很多,比如權(quán)限管理、同一賬號(hào)異地登錄。本文只是淺顯的應(yīng)用了一下。
到此這篇關(guān)于Vue3+Vite使用雙token實(shí)現(xiàn)無感刷新的文章就介紹到這了,更多相關(guān)Vue3 無感刷新內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- 前端登錄token失效實(shí)現(xiàn)雙Token無感刷新詳細(xì)步驟
- SpringBoot中雙token實(shí)現(xiàn)無感刷新
- 雙Token實(shí)現(xiàn)無感刷新的完整代碼示例
- 雙token無感刷新nodejs+React詳細(xì)解釋(保姆級(jí)教程)
- node.js實(shí)現(xiàn)雙Token+Cookie存儲(chǔ)+無感刷新機(jī)制的示例
- 雙Token無感刷新機(jī)制實(shí)現(xiàn)方式
- 前端雙token無感刷新圖文詳解
- vue中雙token和無感刷新token的區(qū)別
- Vue實(shí)現(xiàn)雙token無感刷新的示例代碼
- SpringBoot+React中雙token實(shí)現(xiàn)無感刷新
相關(guān)文章
vue學(xué)習(xí)筆記之指令v-text && v-html && v-bind詳解
這篇文章主要介紹了vue學(xué)習(xí)筆記之指令v-text && v-html && v-bind詳解,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2017-05-05
vue3+elementPlus二次封裝表單的實(shí)現(xiàn)代碼
最近使用Vue3+ElementPlus開發(fā)項(xiàng)目,從整體上構(gòu)思組件的封裝。能寫成組件的內(nèi)容都進(jìn)行封裝,方便多個(gè)地方使用,這篇文章給大家介紹了vue3+elementPlus二次封裝表單的實(shí)現(xiàn),并通過代碼介紹的非常詳細(xì),需要的朋友可以參考下2024-03-03
在項(xiàng)目中封裝axios的實(shí)戰(zhàn)過程
這篇文章主要給大家介紹了關(guān)于如何在項(xiàng)目中封裝axios的相關(guān)資料,axios 請(qǐng)求的封裝,無非是為了方便代碼管理,文中通過示例代碼介紹的非常詳細(xì),需要的朋友可以參考下2021-09-09
Vue項(xiàng)目打包部署到GitHub Pages的實(shí)現(xiàn)步驟
本文主要介紹了Vue項(xiàng)目打包部署到GitHub Pages的實(shí)現(xiàn)步驟,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2022-04-04
詳細(xì)聊聊前端如何實(shí)現(xiàn)token無感刷新(refresh_token)
實(shí)現(xiàn)token無感刷新對(duì)于前端來說是一項(xiàng)非常常用的技術(shù),其本質(zhì)是為了優(yōu)化用戶體驗(yàn),下面這篇文章主要給大家介紹了關(guān)于前端如何實(shí)現(xiàn)token無感刷新(refresh_token)的相關(guān)資料,需要的朋友可以參考下2022-11-11

