亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

Vue實(shí)現(xiàn)雙token無感刷新的示例代碼

 更新時(shí)間:2024年03月08日 09:55:17   作者:RCX明  
這篇文章主要介紹了Vue實(shí)現(xiàn)雙token無感刷新,雙token機(jī)制,尤其是指在OAuth 2.0授權(quán)協(xié)議中廣泛使用的access token(訪問令牌)和refresh token(刷新令牌)組合,文中通過代碼示例講解的非常詳細(xì),需要的朋友可以參考下

雙token機(jī)制,尤其是指在OAuth 2.0授權(quán)協(xié)議中廣泛使用的access token(訪問令牌)和refresh token(刷新令牌)組合,用來實(shí)現(xiàn)無感刷新登錄狀態(tài)的原理如下:

初次授權(quán)與發(fā)放Token:

用戶登錄時(shí),通過用戶名、密碼或其他認(rèn)證方式向認(rèn)證服務(wù)器請(qǐng)求授權(quán)。認(rèn)證成功后,服務(wù)器不僅返回一個(gè)短期有效的access token(通常幾分鐘到幾小時(shí)),還會(huì)發(fā)放一個(gè)長(zhǎng)期有效的refresh token(幾天到幾個(gè)月)。

Access Token的作用:

access token是客戶端訪問受保護(hù)資源的臨時(shí)憑證,每次客戶端發(fā)起對(duì)受保護(hù)資源的請(qǐng)求時(shí),都需要在HTTP請(qǐng)求頭中攜帶access token。一旦access token過期,請(qǐng)求就會(huì)失敗。

Refresh Token的作用:

refresh token的目的是在access token過期后,無需用戶重新登錄,客戶端可以使用refresh token向認(rèn)證服務(wù)器申請(qǐng)新的access token。通常refresh token的生命周期較長(zhǎng),而且存儲(chǔ)得更為安全,因?yàn)樗婕暗介L(zhǎng)期的授權(quán)。

無感刷新:

當(dāng)客戶端檢測(cè)到access token即將過期或已經(jīng)過期時(shí),自動(dòng)在后臺(tái)向認(rèn)證服務(wù)器發(fā)起請(qǐng)求,攜帶refresh token換取新的access token。這個(gè)過程對(duì)用戶來說是無感知的,即用戶不需要重新登錄,頁面也不會(huì)中斷或刷新,因此被稱為“無感刷新”。

安全機(jī)制:

為了保證安全性,refresh token一般具備一定的安全措施,例如限制其使用次數(shù)(防止無限刷新)、設(shè)置有效期(過期后必須重新登錄)以及嚴(yán)格的存儲(chǔ)策略(通常不會(huì)在客戶端明文存儲(chǔ),而是存儲(chǔ)在服務(wù)器端或經(jīng)過加密存儲(chǔ)在客戶端本地)。

通過這種雙token機(jī)制,可以在保障用戶隱私和安全性的同時(shí),大大提升用戶體驗(yàn),讓用戶在長(zhǎng)時(shí)間操作過程中無需反復(fù)登錄,實(shí)現(xiàn)所謂的“無感刷新登錄狀態(tài)”。

后端創(chuàng)建nest項(xiàng)目

# 創(chuàng)建
npx nest new token-test
#運(yùn)行
cd token-test
npm run start

AppController 添加login、refresh、getinfo接口

// 登錄請(qǐng)求
  @Post('api/login')
  login(@Body() userDto: UserDto) {

    console.log(userDto);
    const user = users.find(item => item.username === userDto.username);

    if (!user) {
      throw new BadRequestException('用戶不存在');
    }

    if (user.password !== userDto.password) {
      throw new BadRequestException("密碼錯(cuò)誤");
    }

    const accessToken = this.jwtService.sign({
      username: user.username,
      email: user.email
    }, {
      expiresIn: '0.5h'
    });
    //access_token 過期時(shí)間半小時(shí)
    const refreshToken = this.jwtService.sign({
      username: user.username
    }, {
      expiresIn: '7d'
    })
    //refresh_token 過期時(shí)間 7 天
    return {
      userInfo: {
        username: user.username,
        email: user.email
      },
      accessToken,
      refreshToken
    };
  }

  // 刷新token請(qǐng)求
  @Post('api/refresh')
  refresh(@Body() body: any) {
    try {
      console.log('refresh token');
      console.log(body.token);
      const data = this.jwtService.verify(body.token);

      const user = users.find(item => item.username === data.username);

      const accessToken = this.jwtService.sign({
        username: user.username,
        email: user.email
      }, {
        expiresIn: '0.5h'
      });

      const refreshToken = this.jwtService.sign({
        username: user.username
      }, {
        expiresIn: '7d'
      })
      return {
        accessToken,
        refreshToken
      };
    } catch (e) {
      throw new UnauthorizedException('token 失效,請(qǐng)重新登錄');
    }
  }

  // 驗(yàn)證token獲取用戶信息
  @Get('api/getinfo')
  getinfo(@Req() req: Request) {
    const authorization = req.headers['authorization'];

    if (!authorization) {
      throw new UnauthorizedException('用戶未登錄');
    }
    try {
      const token = authorization.split(' ')[1];
      const data = this.jwtService.verify(token);

      return {
        userInfo: {
          username: data.username,
          email: data.email
        }
      };
    } catch (e) {
      throw new UnauthorizedException('token 失效,請(qǐng)重新登錄');
    }
  }

在這里插入圖片描述

在這里插入圖片描述

在這里插入圖片描述

創(chuàng)建user.dto.ts

export class UserDto {
    username: string;
    password: string;
}

在這里插入圖片描述

AppController添加模擬數(shù)據(jù)

const users = [
  { username: 'test', password: 'success', email: 'abc@163.com' }
]

在這里插入圖片描述

前端Hbuilder創(chuàng)建VUE3項(xiàng)目

安裝axios

pnpm i axios

src目錄下創(chuàng)建以下兩個(gè)文件

utils/request.js

//request.js
import axios from "axios";
import { resolveResError } from "./helpers";

const server = axios.create({
	baseURL: "/api",
	timeout: 1000 * 10,
	headers: {
		"Content-type": "application/json"
	}
})
var requesting = false
/*請(qǐng)求攔截器*/
function reqResolve(config) {
	let accessToken = localStorage.getItem('access_token')
	if (accessToken) {
		config.headers.Authorization = 'Bearer ' + accessToken
	}
	return config
}

function reqReject(error) {
	return Promise.reject(error)
}

const SUCCESS_CODES = [0, 200, 201, 202, 203, 204, 205]
/*響應(yīng)攔截器*/
function resResolve(response) {
	const { data, status, config, statusText, headers } = response
	if (headers['content-type']?.includes('json')) {
		//獲取狀態(tài)碼
		const code = data?.code ?? status
		//檢查是否保持
		if (SUCCESS_CODES.includes(code)) {
			return Promise.resolve(data)
		}

		// 根據(jù)code處理對(duì)應(yīng)的操作,并返回處理后的message
		const message = resolveResError(code, data?.message ?? statusText)
			//需要錯(cuò)誤提醒(是否不需要提示)

			!config?.noNeedTip && message && window.$message?.error(message)

		return Promise.reject({ code, message, error: data ?? response })
	}
	return Promise.resolve(data ?? response)
}

async function resReject(error) {
	if (!error || !error.response) {
		const code = error?.code
		/** 根據(jù)code處理對(duì)應(yīng)的操作,并返回處理后的message */
		const message = resolveResError(code, error.message)
		window.$message?.error(message)
		return Promise.reject({ code, message, error })
	}
	const { data, status, config } = error.response
	const code = data?.code ?? status
	const message = resolveResError(code, data?.message ?? error.message)
	let originalRequest = error.config;
	let refreshToken = localStorage.getItem('refresh_token');
	switch (code) {
		case 400:
			if (message == '用戶不存在') {
				return Promise.reject({ code, message, error })
			}
			break;
		case 401:
			if (refreshToken && !originalRequest._retry && !requesting) {
				originalRequest._retry = true;
				requesting = true
				try {
					// 使用refresh token嘗試獲取新的tokens/
					refreshToken = localStorage.getItem('refresh_token');
					console.log("刷新refreshToken");
					console.log(refreshToken);
					
					const refreshResponse = await axios.post('/api/refresh', {
						"token": refreshToken
					}).then((res) => {
						return res;
					}).catch((e) => {
						// 刷新token失效會(huì)跳轉(zhuǎn)下面的catch
						return e;
					})

					if (refreshResponse?.data.accessToken) {
						localStorage.setItem('access_token', refreshResponse.data.accessToken);
						localStorage.setItem('refresh_token', refreshResponse.data.refreshToken);
						// 在原始請(qǐng)求中添加新的access token,并標(biāo)記為重試請(qǐng)求
						originalRequest.headers.Authorization = `Bearer ${refreshResponse.accessToken}`;
						requesting = false
						// 重新發(fā)起請(qǐng)求
						return await server(originalRequest);
					}
				} catch (refreshError) {
					// 若刷新token失敗,清除存儲(chǔ)的tokens并通知用戶重新登錄
					localStorage.removeItem('access_token');
					localStorage.removeItem('refresh_token');
					alert('登錄過期,請(qǐng)重新登錄');
					console.log("刷新token失敗");
					requesting = false
				}
			} else {
				// 若無refresh token,直接提示用戶重新登錄
				localStorage.removeItem('access_token');
				localStorage.removeItem('refresh_token');
				console.log("無刷新token");
				alert('登錄過期,請(qǐng)重新登錄');
			}
			break;
		case 403:
			console.log("沒有權(quán)限");
			break;
	}
	/** 需要錯(cuò)誤提醒 */
	!config?.noNeedTip && message && window.$message?.error(message)
	return Promise.reject({ code, message, error: error.response?.data || error.response })
}
server.interceptors.request.use(reqResolve, reqReject)
server.interceptors.response.use(resResolve, resReject)

export default server

unitls/helper.js

export function resolveResError(code, message) {
	switch (code) {
		case 401:
			message = '登錄已過期,是否重新登錄'
			break
		case 11007:
		case 11008:
			message = '退出登錄'
			break
		case 403:
			message = '請(qǐng)求被拒絕'
			break
		case 404:
			message = '請(qǐng)求資源或接口不存在'
			break
		case 500:
			message = '服務(wù)器發(fā)生異常'
			break
		default:
			message = message ?? `【$[code]】: 未知異常!`
			break
	}
	return message
}

根目錄下添加.env配置環(huán)境

VITE_TITLE = '待煎的閑魚'
# 是否使用Hash路由
VITE_USE_HASH = 'true'

# 資源公共路徑,需要以 /開頭和結(jié)尾
VITE_PUBLIC_PATH = '/'

# 代理配置-target 本地服務(wù)
VITE_PROXY_TARGET = 'http://localhost:3000' 

根目錄下創(chuàng)建vite.config.js配置代理

import path from 'path'
import { defineConfig, loadEnv } from 'vite'
import Vue from '@vitejs/plugin-vue'

// https://vitejs.dev/config/
export default defineConfig(({ command, mode }) => {
	const isBuild = command === 'build'
	const viteEnv = loadEnv(mode, process.cwd())
	const { VITE_TITLE, VITE_PUBLIC_PATH, VITE_PROXY_TARGET } = viteEnv
	return {
		plugins: [Vue()],
		base: VITE_PUBLIC_PATH || '/',
		resolve: {
			alias: {
				'@': path.resolve(process.cwd(), 'src'),
				'~': path.resolve(process.cwd()),
			},
		},
		server: {
			port: 3200, // 設(shè)置服務(wù)啟動(dòng)端口號(hào)
			// open: true, // 設(shè)置服務(wù)啟動(dòng)時(shí)是否自動(dòng)打開瀏覽器
			cors: true, // 允許跨域
			// 設(shè)置代理,根據(jù)我們項(xiàng)目實(shí)際情況配置
			proxy: {
				'/api': { //api是自行設(shè)置的請(qǐng)求前綴,按照這個(gè)來匹配請(qǐng)求,有這個(gè)字段的請(qǐng)求,就會(huì)進(jìn)到代理來
					target: "http://localhost:3000", //是自己需要調(diào)的接口的前綴域名
					ws: false,
					changeOrigin: true
				},
			}
		}
	}
})

在這里插入圖片描述

以上就是Vue實(shí)現(xiàn)雙token無感刷新的示例代碼的詳細(xì)內(nèi)容,更多關(guān)于Vue雙token無感刷新的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • vue移動(dòng)端實(shí)現(xiàn)手指滑動(dòng)效果

    vue移動(dòng)端實(shí)現(xiàn)手指滑動(dòng)效果

    這篇文章主要為大家詳細(xì)介紹了vue移動(dòng)端實(shí)現(xiàn)手指滑動(dòng)效果,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2021-06-06
  • Vue中created和mounted使用詳解

    Vue中created和mounted使用詳解

    Vue中生命周期包括多個(gè)階段,如created和mounted,每階段有特定鉤子函數(shù),生命周期與瀏覽器渲染過程密切相關(guān),了解這些可以優(yōu)化頁面渲染和數(shù)據(jù)處理,created階段適用于數(shù)據(jù)初始化,而mounted階段適合進(jìn)行DOM操作和頁面渲染后的處理
    2024-10-10
  • Vue3在router中使用pinia報(bào)錯(cuò)的簡(jiǎn)單解決辦法

    Vue3在router中使用pinia報(bào)錯(cuò)的簡(jiǎn)單解決辦法

    這篇文章主要給大家介紹了關(guān)于Vue3在router中使用pinia報(bào)錯(cuò)的簡(jiǎn)單解決辦法,什么是pinia,可以理解為狀態(tài)管理工具,文中通過圖文介紹的非常詳細(xì),需要的朋友可以參考下
    2023-08-08
  • Vue中動(dòng)態(tài)添加ref的方法詳解

    Vue中動(dòng)態(tài)添加ref的方法詳解

    在Vue.js項(xiàng)目中,ref是一個(gè)非常有用的功能,它可以用來獲取DOM元素或子組件的實(shí)例引用,通過ref,我們可以在父組件中直接操作子組件的方法和屬性,或者對(duì)DOM元素進(jìn)行直接操作,本文將詳細(xì)介紹如何在Vue中動(dòng)態(tài)添加ref,并通過多個(gè)具體的代碼示例來幫助讀者理解其實(shí)現(xiàn)過程
    2024-10-10
  • vue3?中?computed?新用法示例小結(jié)

    vue3?中?computed?新用法示例小結(jié)

    這篇文章主要介紹?vue3?中?computed?的新用法,對(duì)比?vue2?中的寫法,讓您快速掌握?vue3?中?computed?的新用法,對(duì)函數(shù)式寫法,options?寫法相關(guān)知識(shí)感興趣的朋友一起看看吧
    2021-11-11
  • vue封裝全局彈窗警告組件this.$message.success問題

    vue封裝全局彈窗警告組件this.$message.success問題

    這篇文章主要介紹了vue封裝全局彈窗警告組件this.$message.success問題,具有很的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2023-09-09
  • Vue中Mustache插值語法與v-bind指令詳解

    Vue中Mustache插值語法與v-bind指令詳解

    在Vue中通過Mustache語法(雙大括號(hào))將data中的文本數(shù)據(jù)插入到HTML中,下面這篇文章主要給大家介紹了關(guān)于Vue中Mustache插值語法與v-bind指令的相關(guān)資料,文中通過實(shí)例代碼介紹的非常詳細(xì),需要的朋友可以參考下
    2022-10-10
  • vue中ref標(biāo)簽屬性和$ref的關(guān)系解讀

    vue中ref標(biāo)簽屬性和$ref的關(guān)系解讀

    這篇文章主要介紹了vue中ref標(biāo)簽屬性和$ref的關(guān)系,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2023-07-07
  • vue3中pinia的使用方法

    vue3中pinia的使用方法

    Pinia是Vue3的狀態(tài)管理工具,安裝后在入口文件引入,定義store并在組件中使用,本文就來介紹一下vue3中pinia的使用方法,感興趣的可以了解一下
    2024-10-10
  • vue組件發(fā)布成npm包

    vue組件發(fā)布成npm包

    平常使用Vue開發(fā)時(shí),一個(gè)項(xiàng)目中多個(gè)地方需要用到的相同組件通常我們會(huì)封裝為一個(gè)公共組件,所以我們可以將封裝好的組件打包發(fā)布至npm,本文主要介紹了vue組件發(fā)布成npm包,具有一定的參考價(jià)值,感興趣的可以了解一下
    2024-01-01

最新評(píng)論