Vue3+Vite使用雙token實現(xiàn)無感刷新
前言
近期寫的一個項目使用雙token實現(xiàn)無感刷新。最后做了一些總結(jié),本文詳細(xì)介紹了實現(xiàn)流程,前后端詳細(xì)代碼。前端使用了Vue3+Vite,主要是axios封裝,服務(wù)端使用了koa2做了一個簡單的服務(wù)器模擬。
一、token 登錄鑒權(quán)
jwt:JSON Web Token。是一種認(rèn)證協(xié)議,一般用來校驗請求的身份信息和身份權(quán)限。 由三部分組成:Header、Hayload、Signature
header:也就是頭部信息,是描述這個 token 的基本信息,json 格式
{ "alg": "HS256", // 表示簽名的算法,默認(rèn)是 HMAC SHA256(寫成 HS256) "type": "JWT" // 表示Token的類型,JWT 令牌統(tǒng)一寫為JWT }
payload:載荷,也是一個 JSON 對象,用來存放實際需要傳遞的數(shù)據(jù)。不建議存放敏感信息,比如密碼。
{ "iss": "a.com", // 簽發(fā)人 "exp": "1d", // expiration time 過期時間 "sub": "test", // 主題 "aud": "", // 受眾 "nbf": "", // Not Before 生效時間 "iat": "", // Issued At 簽發(fā)時間 "jti": "", // JWT ID 編號 // 可以定義私有字段 "name": "", "admin": "" }
Signature 簽名 是對前兩部分的簽名,防止數(shù)據(jù)被篡改。 需要指定一個密鑰。這個密鑰只有服務(wù)器才知道,不能泄露。使用 Header 里面指定的簽名算法,按照公式產(chǎn)生簽名。
算出簽名后,把 Header、Payload、Signature 三個部分拼成的一個字符串,每個部分之間用 . 分隔。這樣就生成了一個 token
二、何為雙 token
accessToken
:用戶獲取數(shù)據(jù)權(quán)限refreshToken
:用來獲取新的accessToken
雙 token 驗證機(jī)制,其中 accessToken 過期時間較短,refreshToken 過期時間較長。當(dāng) accessToken 過期后,使用 refreshToken 去請求新的 token。
雙 token 驗證流程
- 用戶登錄向服務(wù)端發(fā)送賬號密碼,登錄失敗返回客戶端重新登錄。登錄成功服務(wù)端生成 accessToken 和 refreshToken,返回生成的 token 給客戶端。
- 在請求攔截器中,請求頭中攜帶 accessToken 請求數(shù)據(jù),服務(wù)端驗證 accessToken 是否過期。token 有效繼續(xù)請求數(shù)據(jù),token 失效返回失效信息到客戶端。
- 客戶端收到服務(wù)端發(fā)送的請求信息,在二次封裝的 axios 的響應(yīng)攔截器中判斷是否有 accessToken 失效的信息,沒有返回響應(yīng)的數(shù)據(jù)。有失效的信息,就攜帶 refreshToken 請求新的 accessToken。
- 服務(wù)端驗證 refreshToken 是否有效。有效,重新生成 token, 返回新的 token 和提示信息到客戶端,無效,返回?zé)o效信息給客戶端。
- 客戶端響應(yīng)攔截器判斷響應(yīng)信息是否有 refreshToken 有效無效。無效,退出當(dāng)前登錄。有效,重新存儲新的 token,繼續(xù)請求上一次請求的數(shù)據(jù)。
注意事項
- 短token失效,服務(wù)端拒絕請求,返回token失效信息,前端請求到新的短token如何再次請求數(shù)據(jù),達(dá)到無感刷新的效果。
- 服務(wù)端白名單,成功登錄前是還沒有請求到token的,那么如果服務(wù)端攔截請求,就無法登錄。定制白名單,讓登錄無需進(jìn)行token驗證。
三、服務(wù)端代碼
1. 搭建koa2服務(wù)器
全局安裝koa腳手架
npm install koa-generator -g
創(chuàng)建服務(wù)端 直接koa2+項目名
koa2 server
cd server 進(jìn)入到項目安裝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 過期時間,時間單位是秒 也可以這么寫 expiresIn:1d 代表一天 1h 代表一小時 */ // 本次是為了測試,所以設(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)建的項目已經(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 } }) /*驗證長token是否有效,刷新短token 這里要注意,在刷新短token的時候回也返回新的長token,延續(xù)長token, 這樣活躍用戶在持續(xù)操作過程中不會被迫退出登錄。長時間無操作的非活 躍用戶長token過期重新登錄 */ router.get('/refresh',(ctx)=>{ let code,msg,data=null //獲取請求頭中攜帶的長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無效,請重新登錄' } 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驗證*/ 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)){ // 執(zhí)行下一步 return await next() } else { // 獲取請求頭攜帶的短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)
其實如果只是做一個簡單的雙token驗證,很多中間件是沒必要的,比如解析靜態(tài)資源。不過為了節(jié)省時間,方便就直接使用了koa2腳手架。
最終目錄結(jié)構(gòu):
四、前端代碼
1. Vue3+Vite框架
前端使用了Vue3+Vite的框架,看個人使用習(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. 存儲、調(diào)用過期請求
關(guān)鍵點:把攜帶過期token的請求,利用Promise存在數(shù)組中,保持pending狀態(tài),也就是不調(diào)用resolve()。當(dāng)獲取到新的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 // 設(shè)置開關(guān),保證一次只能請求一次短token,防止客戶多此操作,多次請求 /*把過期請求添加在數(shù)組中*/ export const addRequest = (request) => { subscribes.push(request) } /*調(diào)用過期請求*/ export const retryRequest = () => { console.log('重新請求上次中斷的數(shù)據(jù)'); 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 // 重新請求數(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" } }) /*請求攔截器*/ server.interceptors.request.use(config => { // 獲取短token,攜帶到請求頭,服務(wù)端校驗 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) // 把過期請求存儲起來,用于請求到新的短token,再次請求,達(dá)到無感刷新 addRequest(() => resolve(server(config))) // 攜帶長token去請求新的token refreshToken() } else { // 有效返回相應(yīng)的數(shù)據(jù) resolve(data) } }) }, error => { return Promise.reject(error) } )
5. 復(fù)用封裝
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' }) } /*請求數(shù)據(jù)*/ export const getData = () => { return server({ url: '/getList', method: 'get' }) }
項目運行
最后的最后,運行項目,查看效果 后端設(shè)置的短token5秒,長token10秒。登錄請求到token后,請求數(shù)據(jù)可以正常請求,五秒后再次請求,短token失效,這時長token有效,請求到新的token,refresh接口只調(diào)用了一次。長token也過期后,就需要重新登錄啦。
寫在最后
這就是一整套的前后端使用雙token機(jī)制實現(xiàn)無感刷新。token能做到的還有很多,比如權(quán)限管理、同一賬號異地登錄。本文只是淺顯的應(yīng)用了一下。
到此這篇關(guān)于Vue3+Vite使用雙token實現(xiàn)無感刷新的文章就介紹到這了,更多相關(guān)Vue3 無感刷新內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Vue中div contenteditable 的光標(biāo)定位方法
今天小編就為大家分享一篇Vue中div contenteditable 的光標(biāo)定位方法,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2018-08-08vue+elementui 實現(xiàn)上傳文件/導(dǎo)入文件的功能示例詳解
文章介紹了如何使用Vue和Element UI實現(xiàn)上傳文件和導(dǎo)入文件的功能,主要內(nèi)容包括:上傳組件的使用、data中的數(shù)據(jù)存儲、methods中的方法(選擇文件、點擊確定上傳文件、刪除文件),文章還提供了相關(guān)鏈接供進(jìn)一步學(xué)習(xí),感興趣的朋友一起看看吧2025-03-03vue本地構(gòu)建熱更新卡頓的問題“75?advanced?module?optimization”完美解決方案
這篇文章主要介紹了vue本地構(gòu)建熱更新卡頓的問題“75?advanced?module?optimization”解決方案,每次熱更新都會卡在?"75?advanced?module?optimization"?的地方不動了,如何解決這個問題呢,下面小編給大家?guī)砹私鉀Q方案,需要的朋友可以參考下2022-08-08vue+element表格實現(xiàn)多層數(shù)據(jù)的嵌套方式
這篇文章主要介紹了vue+element表格實現(xiàn)多層數(shù)據(jù)的嵌套方式,具有很好的參考價值。希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-09-09使用element-ui,el-row中的el-col數(shù)據(jù)為空頁面布局變亂問題
這篇文章主要介紹了使用element-ui,el-row中的el-col數(shù)據(jù)為空頁面布局變亂問題,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2024-08-08vue element-ui導(dǎo)航實現(xiàn)全屏/取消全屏功能
這篇文章主要介紹了vue element-ui導(dǎo)航實現(xiàn)全屏/取消全屏功能,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2024-08-08