最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

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

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

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

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

用戶登錄時(shí),通過(guò)用戶名、密碼或其他認(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是客戶端訪問(wèn)受保護(hù)資源的臨時(shí)憑證,每次客戶端發(fā)起對(duì)受保護(hù)資源的請(qǐng)求時(shí),都需要在HTTP請(qǐng)求頭中攜帶access token。一旦access token過(guò)期,請(qǐng)求就會(huì)失敗。

Refresh Token的作用:

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

無(wú)感刷新:

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

安全機(jī)制:

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

通過(guò)這種雙token機(jī)制,可以在保障用戶隱私和安全性的同時(shí),大大提升用戶體驗(yàn),讓用戶在長(zhǎng)時(shí)間操作過(guò)程中無(wú)需反復(fù)登錄,實(shí)現(xiàn)所謂的“無(wú)感刷新登錄狀態(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 過(guò)期時(shí)間半小時(shí)
    const refreshToken = this.jwtService.sign({
      username: user.username
    }, {
      expiresIn: '7d'
    })
    //refresh_token 過(guò)期時(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('登錄過(guò)期,請(qǐng)重新登錄');
					console.log("刷新token失敗");
					requesting = false
				}
			} else {
				// 若無(wú)refresh token,直接提示用戶重新登錄
				localStorage.removeItem('access_token');
				localStorage.removeItem('refresh_token');
				console.log("無(wú)刷新token");
				alert('登錄過(guò)期,請(qǐng)重新登錄');
			}
			break;
		case 403:
			console.log("沒(méi)有權(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 = '登錄已過(guò)期,是否重新登錄'
			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 = '待煎的閑魚(yú)'
# 是否使用Hash路由
VITE_USE_HASH = 'true'

# 資源公共路徑,需要以 /開(kāi)頭和結(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)打開(kāi)瀏覽器
			cors: true, // 允許跨域
			// 設(shè)置代理,根據(jù)我們項(xiàng)目實(shí)際情況配置
			proxy: {
				'/api': { //api是自行設(shè)置的請(qǐng)求前綴,按照這個(gè)來(lái)匹配請(qǐng)求,有這個(gè)字段的請(qǐng)求,就會(huì)進(jìn)到代理來(lái)
					target: "http://localhost:3000", //是自己需要調(diào)的接口的前綴域名
					ws: false,
					changeOrigin: true
				},
			}
		}
	}
})

在這里插入圖片描述

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

相關(guān)文章

  • Vscode關(guān)閉Eslint語(yǔ)法檢查的多種方式(保證有效)

    Vscode關(guān)閉Eslint語(yǔ)法檢查的多種方式(保證有效)

    eslint是一個(gè)JavaScript的校驗(yàn)插件,通常用來(lái)校驗(yàn)語(yǔ)法或代碼的書(shū)寫風(fēng)格,下面這篇文章主要給大家介紹了關(guān)于Vscode關(guān)閉Eslint語(yǔ)法檢查的多種方式,文章通過(guò)圖文介紹的非常詳細(xì),需要的朋友可以參考下
    2022-07-07
  • vue+element實(shí)現(xiàn)打印頁(yè)面功能

    vue+element實(shí)現(xiàn)打印頁(yè)面功能

    這篇文章主要介紹了vue+element實(shí)現(xiàn)打印頁(yè)面功能,本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),具有一定的參考借鑒價(jià)值 ,需要的朋友可以參考下
    2019-05-05
  • 使用Vue-Router 2實(shí)現(xiàn)路由功能實(shí)例詳解

    使用Vue-Router 2實(shí)現(xiàn)路由功能實(shí)例詳解

    vue-router 2只適用于Vue2.x版本,下面我們是基于vue2.0講的如何使用vue-router 2實(shí)現(xiàn)路由功能,需要的朋友可以參考下
    2017-11-11
  • vue項(xiàng)目實(shí)現(xiàn)局部全屏完整代碼

    vue項(xiàng)目實(shí)現(xiàn)局部全屏完整代碼

    最近需要做一個(gè)全屏功能,所以這里給大家總結(jié)下,這篇文章主要給大家介紹了關(guān)于vue項(xiàng)目實(shí)現(xiàn)局部全屏的相關(guān)資料,需要的朋友可以參考下
    2023-09-09
  • vue-router中關(guān)于meta的作用及說(shuō)明

    vue-router中關(guān)于meta的作用及說(shuō)明

    這篇文章主要介紹了vue-router中關(guān)于meta的作用及說(shuō)明,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2023-05-05
  • Vue.js用法詳解

    Vue.js用法詳解

    Vue.js(讀音 /vjuː/, 類似于 view) 是一套構(gòu)建用戶界面的漸進(jìn)式框架。這篇文章主要介紹了Vue.js用法詳解,需要的朋友可以參考下
    2017-11-11
  • vue+element-plus上傳圖片及回顯問(wèn)題及數(shù)量限制

    vue+element-plus上傳圖片及回顯問(wèn)題及數(shù)量限制

    本文主要介紹了vue+element-plus上傳圖片及回顯問(wèn)題及數(shù)量限制,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2023-04-04
  • Vue圖片懶加載之Vue-Lazyload的使用

    Vue圖片懶加載之Vue-Lazyload的使用

    懶加載就是延時(shí)加載,即當(dāng)需要用到的時(shí)候再去加載,本文主要介紹了Vue圖片懶加載之Vue-Lazyload的使用,具有一定的參考價(jià)值,感興趣的可以了解一下
    2024-05-05
  • 如何使用Vue的思想封裝一個(gè)Storage

    如何使用Vue的思想封裝一個(gè)Storage

    作為Web Storage API的接口,Storage 提供了訪問(wèn)特定域名下的會(huì)話存儲(chǔ)或本地存儲(chǔ)的功能,例如可以添加、修改或刪除存儲(chǔ)的數(shù)據(jù)項(xiàng),這篇文章主要給大家介紹了關(guān)于如何使用Vue的思想封裝一個(gè)Storage的相關(guān)資料,需要的朋友可以參考下
    2021-08-08
  • 解決vue項(xiàng)目F5刷新mounted里的函數(shù)不執(zhí)行問(wèn)題

    解決vue項(xiàng)目F5刷新mounted里的函數(shù)不執(zhí)行問(wèn)題

    今天小編就為大家分享一篇解決vue項(xiàng)目F5刷新mounted里的函數(shù)不執(zhí)行問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧
    2019-11-11

最新評(píng)論

宜都市| 盘锦市| 和平区| 三都| 潍坊市| 阿拉善盟| 伊吾县| 西昌市| 荥经县| 浪卡子县| 松阳县| 大化| 江达县| 广汉市| 卢龙县| 东乡| 福贡县| 阳山县| 开封市| 石城县| 逊克县| 邻水| 米易县| 临汾市| 郸城县| 荃湾区| 云和县| 武隆县| 伊吾县| 元氏县| 化隆| 台北市| 宣威市| 佳木斯市| 平南县| 高安市| 宣汉县| 正阳县| 宽城| 洛川县| 恩施市|