首頁 > web前端 > Vue.js > 主體

Vue3+Vite怎麼使用雙token實現無感刷新

WBOY
發布: 2023-05-10 13:10:06
轉載
2051 人瀏覽過

一、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 跨網域Vue3+Vite怎麼使用雙token實現無感刷新

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
登入後複製

安裝axiosVue3+Vite怎麼使用雙token實現無感刷新

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
登入後複製
Vue3+Vite怎麼使用雙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也過期後,就需要重新登入啦。 ############

以上是Vue3+Vite怎麼使用雙token實現無感刷新的詳細內容。更多資訊請關注PHP中文網其他相關文章!

相關標籤:
來源:yisu.com
本網站聲明
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn
熱門教學
更多>
最新下載
更多>
網站特效
網站源碼
網站素材
前端模板