溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

Vue3+Vite如何使用雙token實(shí)現(xiàn)無(wú)感刷新

發(fā)布時(shí)間:2023-05-10 14:14:40 來(lái)源:億速云 閱讀:122 作者:zzz 欄目:編程語(yǔ)言

本篇內(nèi)容主要講解“Vue3+Vite如何使用雙token實(shí)現(xiàn)無(wú)感刷新”,感興趣的朋友不妨來(lái)看看。本文介紹的方法操作簡(jiǎn)單快捷,實(shí)用性強(qiáng)。下面就讓小編來(lái)帶大家學(xué)習(xí)“Vue3+Vite如何使用雙token實(shí)現(xiàn)無(wú)感刷新”吧!

一、token 登錄鑒權(quán)

jwt:JSON Web Token。是一種認(rèn)證協(xié)議,一般用來(lái)校驗(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ì)象,用來(lái)存放實(shí)際需要傳遞的數(shù)據(jù)。不建議存放敏感信息,比如密碼。

{
  "iss": "a.com", // 簽發(fā)人
  "exp": "1d", // expiration time 過(guò)期時(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:用來(lái)獲取新的accessToken

雙 token 驗(yàn)證機(jī)制,其中 accessToken 過(guò)期時(shí)間較短,refreshToken 過(guò)期時(shí)間較長(zhǎng)。當(dāng) accessToken 過(guò)期后,使用 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 是否過(guò)期。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 和提示信息到客戶端,無(wú)效,返回?zé)o效信息給客戶端。

  • 客戶端響應(yīng)攔截器判斷響應(yīng)信息是否有 refreshToken 有效無(wú)效。無(wú)效,退出當(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ú)感刷新的效果。

  • 服務(wù)端白名單,成功登錄前是還沒有請(qǐng)求到token的,那么如果服務(wù)端攔截請(qǐng)求,就無(wú)法登錄。定制白名單,讓登錄無(wú)需進(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 過(guò)期時(shí)間,時(shí)間單位是秒
也可以這么寫 expiresIn:1d 代表一天 
1h 代表一小時(shí)
*/
// 本次是為了測(cè)試,所以設(shè)置時(shí)間 短token5秒 長(zhǎng)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
    }
})

/*用于測(cè)試的獲取數(shù)據(jù)接口*/
router.get('/getTestData',(ctx)=>{
    let code,msg,data=null
    code=2000
    msg='獲取數(shù)據(jù)成功'
    ctx.body={
        code,
        msg,
        data
    }
})

/*驗(yàn)證長(zhǎng)token是否有效,刷新短token
  這里要注意,在刷新短token的時(shí)候回也返回新的長(zhǎng)token,延續(xù)長(zhǎng)token,
  這樣活躍用戶在持續(xù)操作過(guò)程中不會(huì)被迫退出登錄。長(zhǎng)時(shí)間無(wú)操作的非活
  躍用戶長(zhǎng)token過(guò)期重新登錄
*/
router.get('/refresh',(ctx)=>{
    let code,msg,data=null
    //獲取請(qǐng)求頭中攜帶的長(zhǎ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='長(zhǎng)token無(wú)效,請(qǐng)重新登錄'
        } else{
            code=2000,
            msg='長(zhǎng)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無(wú)效,無(wú)權(quán)限'
            ctx.body={
                code,
                msg,
                data
            }
        } else{
            // 解析token
            await jwt.verify(a_tk,secret.(error)=>{
                if(error)=>{
                      code=4003
                      msg='accessToken無(wú)效,無(wú)權(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è)簡(jiǎn)單的雙token驗(yàn)證,很多中間件是沒必要的,比如解析靜態(tài)資源。不過(guò)為了節(jié)省時(shí)間,方便就直接使用了koa2腳手架。

最終目錄結(jié)構(gòu):

Vue3+Vite如何使用雙token實(shí)現(xiàn)無(wú)感刷新

四、前端代碼

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頭部 攜帶長(zhǎng)token
3. 存儲(chǔ)、調(diào)用過(guò)期請(qǐng)求

關(guān)鍵點(diǎn):把攜帶過(guò)期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)求

/*把過(guò)期請(qǐng)求添加在數(shù)組中*/
export const addRequest = (request) => {
    subscribes.push(request)
}

/*調(diào)用過(guò)期請(qǐng)求*/
export const retryRequest = () => {
    console.log('重新請(qǐng)求上次中斷的數(shù)據(jù)');
    subscribes.forEach(request => request())
    subscribes = []
}

/*短token過(guò)期,攜帶token去重新請(qǐng)求token*/
export const refreshToken=()=>{
    if(!flag){
        flag = true;
        let r_tk = getRefershToken() // 獲取長(zhǎng)token
        if(r_tk){
            server.get('/refresh',Object.assign({},{
                headers:{[PASS]=r_tk}
            })).then((res)=>{
                //長(zhǎng)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)
                // 把過(guò)期請(qǐng)求存儲(chǔ)起來(lái),用于請(qǐng)求到新的短token,再次請(qǐng)求,達(dá)到無(wú)感刷新
                addRequest(() => resolve(server(config)))
                // 攜帶長(zhǎng)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ǔ)長(zhǎng)token
export const setRefershToken = (token) => localStorage.setItem(constants.REFRESH_TOKEN, token)
// 獲取短token
export const getAccessToken = () => localStorage.getItem(constants.ACCESS_TOKEN)
// 獲取長(zhǎng)token
export const getRefershToken = () => localStorage.getItem(constants.REFRESH_TOKEN)
// 刪除短token
export const removeAccessToken = () => localStorage.removeItem(constants.ACCESS_TOKEN)
// 刪除長(zhǎng)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)行

Vue3+Vite如何使用雙token實(shí)現(xiàn)無(wú)感刷新

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

Vue3+Vite如何使用雙token實(shí)現(xiàn)無(wú)感刷新

到此,相信大家對(duì)“Vue3+Vite如何使用雙token實(shí)現(xiàn)無(wú)感刷新”有了更深的了解,不妨來(lái)實(shí)際操作一番吧!這里是億速云網(wǎng)站,更多相關(guān)內(nèi)容可以進(jìn)入相關(guān)頻道進(jìn)行查詢,關(guān)注我們,繼續(xù)學(xué)習(xí)!

向AI問(wèn)一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI