欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

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ù)器請求授權(quán)。認(rèn)證成功后,服務(wù)器不僅返回一個(gè)短期有效的access token(通常幾分鐘到幾小時(shí)),還會(huì)發(fā)放一個(gè)長期有效的refresh token(幾天到幾個(gè)月)。

Access Token的作用:

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

Refresh Token的作用:

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

無感刷新:

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

安全機(jī)制:

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

通過這種雙token機(jī)制,可以在保障用戶隱私和安全性的同時(shí),大大提升用戶體驗(yàn),讓用戶在長時(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接口

// 登錄請求
  @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請求
  @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 失效,請重新登錄');
    }
  }

  // 驗(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 失效,請重新登錄');
    }
  }

在這里插入圖片描述

在這里插入圖片描述

在這里插入圖片描述

創(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
/*請求攔截器*/
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處理對應(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處理對應(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);
						// 在原始請求中添加新的access token,并標(biāo)記為重試請求
						originalRequest.headers.Authorization = `Bearer ${refreshResponse.accessToken}`;
						requesting = false
						// 重新發(fā)起請求
						return await server(originalRequest);
					}
				} catch (refreshError) {
					// 若刷新token失敗,清除存儲(chǔ)的tokens并通知用戶重新登錄
					localStorage.removeItem('access_token');
					localStorage.removeItem('refresh_token');
					alert('登錄過期,請重新登錄');
					console.log("刷新token失敗");
					requesting = false
				}
			} else {
				// 若無refresh token,直接提示用戶重新登錄
				localStorage.removeItem('access_token');
				localStorage.removeItem('refresh_token');
				console.log("無刷新token");
				alert('登錄過期,請重新登錄');
			}
			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 = '請求被拒絕'
			break
		case 404:
			message = '請求資源或接口不存在'
			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)端口號
			// open: true, // 設(shè)置服務(wù)啟動(dòng)時(shí)是否自動(dòng)打開瀏覽器
			cors: true, // 允許跨域
			// 設(shè)置代理,根據(jù)我們項(xiàng)目實(shí)際情況配置
			proxy: {
				'/api': { //api是自行設(shè)置的請求前綴,按照這個(gè)來匹配請求,有這個(gè)字段的請求,就會(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無感刷新的資料請關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • vue點(diǎn)擊單張圖片放大實(shí)現(xiàn)步驟(純js)

    vue點(diǎn)擊單張圖片放大實(shí)現(xiàn)步驟(純js)

    這篇文章主要給大家介紹了關(guān)于vue點(diǎn)擊單張圖片放大實(shí)現(xiàn)的相關(guān)資料,在vue項(xiàng)目中實(shí)現(xiàn)點(diǎn)擊圖片放大功能相信對大家來說都不陌生,文中給出了詳細(xì)的js示例代碼,需要的朋友可以參考下
    2023-07-07
  • Vue下的國際化處理方法

    Vue下的國際化處理方法

    下面小編就為大家分享一篇Vue下的國際化處理方法,具有很好的參考價(jià)值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2017-12-12
  • Vue引用vee-validate插件表單驗(yàn)證問題(cdn方式引用)

    Vue引用vee-validate插件表單驗(yàn)證問題(cdn方式引用)

    這篇文章主要介紹了Vue引用vee-validate插件表單驗(yàn)證問題(cdn方式引用),具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2022-12-12
  • 解讀vue項(xiàng)目防范XSS攻擊問題

    解讀vue項(xiàng)目防范XSS攻擊問題

    這篇文章主要介紹了解讀vue項(xiàng)目防范XSS攻擊問題,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2023-01-01
  • Vue項(xiàng)目中使用v-show和v-if指令以及原理、區(qū)別和適用場景說明

    Vue項(xiàng)目中使用v-show和v-if指令以及原理、區(qū)別和適用場景說明

    Vue中v-show通過display屬性控制顯示隱藏,元素始終存在DOM,適合頻繁切換;v-if條件為false時(shí)移除元素,適合初始條件不成立的情況,性能上首次渲染可能更優(yōu),但切換頻繁時(shí)v-show更高效,其他方式包括綁定style/class、計(jì)算屬性及第三方動(dòng)畫庫
    2025-07-07
  • vant/vue手機(jī)端長按事件以及禁止長按彈出菜單實(shí)現(xiàn)方法詳解

    vant/vue手機(jī)端長按事件以及禁止長按彈出菜單實(shí)現(xiàn)方法詳解

    這篇文章主要介紹了vant/vue手機(jī)端長按事件以及禁止長按彈出菜單實(shí)現(xiàn)方法詳解,需要的朋友可以參考下
    2022-12-12
  • Vue3項(xiàng)目使用PWA技術(shù)進(jìn)行離線加載的詳細(xì)流程

    Vue3項(xiàng)目使用PWA技術(shù)進(jìn)行離線加載的詳細(xì)流程

    PWA是一種融合了 網(wǎng)頁(Web) 與 原生應(yīng)用(Native App) 優(yōu)點(diǎn)的技術(shù), 它讓你用網(wǎng)頁技術(shù)(HTML、CSS、JavaScript)構(gòu)建出一個(gè)能像原生應(yīng)用那樣,簡單來說,就是瀏覽器為你的網(wǎng)頁提供了離線應(yīng)用支持,所以本文給大家介紹了Vue3項(xiàng)目使用PWA技術(shù)進(jìn)行離線加載的過程
    2025-10-10
  • Vue3項(xiàng)目中reset.scss模板使用導(dǎo)入

    Vue3項(xiàng)目中reset.scss模板使用導(dǎo)入

    這篇文章主要為大家介紹了Vue3項(xiàng)目中reset.scss模板使用導(dǎo)入示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-09-09
  • vue項(xiàng)目之頁面class不生效問題及解決

    vue項(xiàng)目之頁面class不生效問題及解決

    這篇文章主要介紹了vue項(xiàng)目之頁面class不生效問題及解決方案,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2023-07-07
  • vue3.0基于views批量實(shí)現(xiàn)動(dòng)態(tài)路由的方法(示例代碼)

    vue3.0基于views批量實(shí)現(xiàn)動(dòng)態(tài)路由的方法(示例代碼)

    以前vue項(xiàng)目中也有很多實(shí)現(xiàn)動(dòng)態(tài)路由的方法,比如有一些項(xiàng)目涉及權(quán)限的可能會(huì)使用api請求路由數(shù)據(jù)在來createRouter,或者本地構(gòu)建使用routes.push來動(dòng)態(tài)構(gòu)建路由, 今天介紹一種新的方式來基于某個(gè)文件夾批量構(gòu)建動(dòng)態(tài)路由的方法,感興趣的朋友一起看看吧
    2024-12-12

最新評論