記錄-使用雙token實現無感刷新,前後端詳細代碼

来源:https://www.cnblogs.com/smileZAZ/archive/2023/04/24/17350349.html
-Advertisement-
Play Games

這裡給大家分享我在網上總結出來的一些知識,希望對大家有所幫助 前言 近期寫的一個項目使用雙token實現無感刷新。最後做了一些總結,本文詳細介紹了實現流程,前後端詳細代碼。前端使用了Vue3+Vite,主要是axios封裝,服務端使用了koa2做了一個簡單的伺服器模擬。 一、token 登錄鑒權 j ...


這裡給大家分享我在網上總結出來的一些知識,希望對大家有所幫助

前言

近期寫的一個項目使用雙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 驗證流程

  1. 用戶登錄向服務端發送賬號密碼,登錄失敗返回客戶端重新登錄。登錄成功服務端生成 accessToken 和 refreshToken,返回生成的 token 給客戶端。
  2. 在請求攔截器中,請求頭中攜帶 accessToken 請求數據,服務端驗證 accessToken 是否過期。token 有效繼續請求數據,token 失效返回失效信息到客戶端。
  3. 客戶端收到服務端發送的請求信息,在二次封裝的 axios 的響應攔截器中判斷是否有 accessToken 失效的信息,沒有返迴響應的數據。有失效的信息,就攜帶 refreshToken 請求新的 accessToken。
  4. 服務端驗證 refreshToken 是否有效。有效,重新生成 token, 返回新的 token 和提示信息到客戶端,無效,返回無效信息給客戶端。
  5. 客戶端響應攔截器判斷響應信息是否有 refreshToken 有效無效。無效,退出當前登錄。有效,重新存儲新的 token,繼續請求上一次請求的數據。

註意事項

  1. 短token失效,服務端拒絕請求,返回token失效信息,前端請求到新的短token如何再次請求數據,達到無感刷新的效果。
  2. 服務端白名單,成功登錄前是還沒有請求到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=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
  在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 = () => {
    return server({
        url: '/getList',
        method: 'get'
    })
}

最後的最後,運行項目,查看效果 後端設置的短token5秒,長token10秒。登錄請求到token後,請求數據可以正常請求,五秒後再次請求,短token失效,這時長token有效,請求到新的token,refresh介面只調用了一次。長token也過期後,就需要重新登錄啦。

本文轉載於:

https://juejin.cn/post/7224764099187736634

如果對您有所幫助,歡迎您點個關註,我會定時更新技術文檔,大家一起討論學習,一起進步。

 


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 實時同步是 ChunJun 的⼀個重要特性,指在數據同步過程中,數據源與⽬標系統之間的數據傳輸和更新⼏乎在同⼀時間進⾏。 在實時同步場景中我們更加關註源端,當源系統中的數據發⽣變化時,這些變化會⽴即傳輸並應⽤到⽬標系統,以保證兩個系統中的數據保持⼀致。這個特性需要作業運⾏過程中 source 插件不 ...
  • 序列(sequence)是 PostgreSQL 中的一種對象,用於生成自動遞增的唯一標識符。通常,序列會與表的自增主鍵一起使用,以確保每個新插入的行都有一個唯一的標識符。在某些情況下,可能需要更新序列的值: 從另一個資料庫中導入數據,自增列的值也從原來的數據中導入。導入的過程中,目標資料庫的序列不 ...
  • 資料庫系統概論—基礎篇(2) 三、關係資料庫標準語言SQL 1、數據定義 1.1基本表的定義、刪除與修改 定義基本表 #建立學生表 CREATE TABLE Student( Sno CHAR(9) PRIMARY KEY, Sname CHAR(20) UNIQUE, Ssex CHAR(2), ...
  • PostgreSQL是一款功能非常強大的開源關係型資料庫,它支持哈希索引、反向索引、部分索引、Expression 索引、GiST、GIN等多種索引模式,同時可安裝功能豐富的擴展包。相較於Mysql,PostgreSQ支持通過PostGIS擴展支持地理空間數據、支持嵌套迴圈,哈希連接,排序合併三種表... ...
  • 1.隱私政策是怎麼樣的?收集哪些信息? 關於Scan Kit的隱私政策及收集的信息,請查看SDK隱私安全說明。 Android:SDK隱私安全說明 iOS:SDK隱私安全說明 2.如何使用多碼識別?多碼模式下如何實現指定條碼?多碼模式的坐標支持返回坐標麽?多碼模式下實現自動放大? 1)統一掃碼服務支 ...
  • 在App開發過程中,如果想實現動畫效果,可以粗略分為兩種方式。一種是直接用代碼編寫,像平移、旋轉等簡單的動畫效果,都可以這麼乾,如果稍微複雜點,就會對開發工程師的數學功底、圖形圖像學功底有很高的要求。 ...
  • 博客園作為一個老牌技術博客網站,有著非常濃郁的游戲開發、引擎開發以及圖形學氛圍。並且沒有像其他網站有難以接受的廣告。 雖說網站主站看著比較老,但還在接受的範圍。並且可以根據自己的喜好來自定義主題。 尋找主題 以下是找的幾個常見的主題,因為是技術博客閱讀體驗是最重要的,所以最終還是選中了silence ...
  • 今天在學習elasticsearch時,遇到一個問題:項目中前端採用的是Vue2+axios,後端的介面採用Restful風格來接收: 關於Resultful風格: 1. GET(SELECT):從伺服器取出資源(一項或多項); 2. POST(CREATE):在伺服器新建一個資源; 3. PUT( ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...