這里給大家分享我在網上總結出來的一些知識,希望對大家有所幫助
前言
近期寫的一個專案使用雙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
}
payload:載荷,也是一個 JSON 物件,用來存放實際需要傳遞的資料,不建議存放敏感資訊,比如密碼,
{
"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+專案名
koa2 server
cd server 進入到專案安裝jwt
npm i jsonwebtoken為了方便直接在服務端使用koa-cors 跨域
npm i koa-cors在app.js中引入應用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=https://www.cnblogs.com/smileZAZ/archive/2023/04/24/null
code=2000
msg='登錄成功,獲取到token'
data=https://www.cnblogs.com/smileZAZ/archive/2023/04/24/{
accessToken:getAccessToken(),
refreshToken:getReferToken()
}
ctx.body={
code,
msg,
data
}
})
/*用于測驗的獲取資料介面*/
router.get('/getTestData',(ctx)=>{
let code,msg,data=https://www.cnblogs.com/smileZAZ/archive/2023/04/24/null
code=2000
msg='獲取資料成功'
ctx.body={
code,
msg,
data
}
})
/*驗證長token是否有效,重繪短token
這里要注意,在重繪短token的時候回也回傳新的長token,延續長token,
這樣活躍用戶在持續操作程序中不會被迫退出登錄,長時間無操作的非活
躍用戶長token過期重新登錄
*/
router.get('/refresh',(ctx)=>{
let code,msg,data=https://www.cnblogs.com/smileZAZ/archive/2023/04/24/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=https://www.cnblogs.com/smileZAZ/archive/2023/04/24/{
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 = https://www.cnblogs.com/smileZAZ/archive/2023/04/24/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
在app.js中引入應用中間件
const auth=requier(./utils/auth) ··· app.use(auth)
其實如果只是做一個簡單的雙token驗證,很多中間件是沒必要的,比如決議靜態資源,不過為了節省時間,方便就直接使用了koa2腳手架,
最終目錄結構:

四、前端代碼
1. Vue3+Vite框架
前端使用了Vue3+Vite的框架,看個人使用習慣,
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. 存盤、呼叫過期請求
關鍵點:把攜帶過期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 = https://www.cnblogs.com/smileZAZ/archive/2023/04/24/() => {
return server({
url:'/getList',
method: 'get'
})
}


本文轉載于:
https://juejin.cn/post/7224764099187736634
如果對您有所幫助,歡迎您點個關注,我會定時更新技術檔案,大家一起討論學習,一起進步,

轉載請註明出處,本文鏈接:https://www.uj5u.com/qiye/551122.html
標籤:其他
上一篇:form-create-designer-naiveui
下一篇:返回列表

