您好,登录后才能下订单哦!
在现代Web应用中,用户认证和授权是一个非常重要的部分。为了确保用户的安全性和数据的隐私性,通常会使用Token机制来进行身份验证。然而,传统的单Token机制存在一些问题,比如Token过期后用户需要重新登录,这会影响用户体验。为了解决这个问题,双Token机制应运而生。
本文将详细介绍如何在Vue3和Vite项目中使用双Token机制实现无感刷新,从而提升用户体验。
双Token机制是指使用两个Token来进行用户认证和授权。通常,这两个Token分别是:
通过使用双Token机制,可以在Access Token过期时,通过Refresh Token自动获取新的Access Token,从而实现无感刷新,避免用户频繁登录。
Vue3是Vue.js的最新版本,带来了许多新特性和改进,如Composition API、更好的TypeScript支持、性能优化等。Vue3的响应式系统更加高效,使得开发者能够更轻松地构建复杂的Web应用。
Vite是一个现代化的前端构建工具,由Vue.js的作者尤雨溪开发。Vite利用浏览器原生ES模块的支持,提供了极快的开发服务器启动速度和热更新。Vite支持Vue、React、Preact等框架,并且具有高度可配置性。
首先,我们需要创建一个新的Vue3项目。使用Vite可以快速初始化一个Vue3项目。
npm create vite@latest my-vue-app --template vue
进入项目目录并安装依赖:
cd my-vue-app
npm install
启动开发服务器:
npm run dev
Vite的配置文件是vite.config.js
,我们可以在这里进行一些自定义配置。例如,配置别名、代理等。
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import path from 'path'
export default defineConfig({
plugins: [vue()],
resolve: {
alias: {
'@': path.resolve(__dirname, './src')
}
},
server: {
proxy: {
'/api': {
target: 'http://localhost:3000',
changeOrigin: true,
rewrite: (path) => path.replace(/^\/api/, '')
}
}
}
})
我们需要安装一些必要的依赖,如axios
用于HTTP请求,vue-router
用于路由管理,pinia
用于状态管理。
npm install axios vue-router pinia
在src/views
目录下创建一个Login.vue
文件,用于实现登录页面。
<template>
<div class="login-container">
<h1>Login</h1>
<form @submit.prevent="handleLogin">
<div class="form-group">
<label for="username">Username</label>
<input type="text" id="username" v-model="username" required />
</div>
<div class="form-group">
<label for="password">Password</label>
<input type="password" id="password" v-model="password" required />
</div>
<button type="submit">Login</button>
</form>
</div>
</template>
<script setup>
import { ref } from 'vue'
import { useRouter } from 'vue-router'
import { useAuthStore } from '@/stores/auth'
const username = ref('')
const password = ref('')
const router = useRouter()
const authStore = useAuthStore()
const handleLogin = async () => {
try {
await authStore.login(username.value, password.value)
router.push('/')
} catch (error) {
alert('Login failed')
}
}
</script>
<style scoped>
.login-container {
max-width: 400px;
margin: 0 auto;
padding: 20px;
border: 1px solid #ccc;
border-radius: 5px;
}
.form-group {
margin-bottom: 15px;
}
label {
display: block;
margin-bottom: 5px;
}
input {
width: 100%;
padding: 8px;
box-sizing: border-box;
}
button {
width: 100%;
padding: 10px;
background-color: #42b983;
color: white;
border: none;
border-radius: 5px;
cursor: pointer;
}
button:hover {
background-color: #3aa876;
}
</style>
在src/stores
目录下创建一个auth.js
文件,用于管理用户认证状态。
import { defineStore } from 'pinia'
import { ref } from 'vue'
import axios from 'axios'
import { useRouter } from 'vue-router'
export const useAuthStore = defineStore('auth', () => {
const accessToken = ref('')
const refreshToken = ref('')
const router = useRouter()
const login = async (username, password) => {
try {
const response = await axios.post('/api/login', { username, password })
accessToken.value = response.data.accessToken
refreshToken.value = response.data.refreshToken
localStorage.setItem('accessToken', accessToken.value)
localStorage.setItem('refreshToken', refreshToken.value)
} catch (error) {
throw error
}
}
const logout = () => {
accessToken.value = ''
refreshToken.value = ''
localStorage.removeItem('accessToken')
localStorage.removeItem('refreshToken')
router.push('/login')
}
return {
accessToken,
refreshToken,
login,
logout
}
})
在src/utils
目录下创建一个auth.js
文件,用于处理双Token的逻辑。
import axios from 'axios'
import { useAuthStore } from '@/stores/auth'
const authStore = useAuthStore()
const axiosInstance = axios.create({
baseURL: '/api',
headers: {
'Content-Type': 'application/json'
}
})
axiosInstance.interceptors.request.use(
(config) => {
const accessToken = authStore.accessToken
if (accessToken) {
config.headers.Authorization = `Bearer ${accessToken}`
}
return config
},
(error) => {
return Promise.reject(error)
}
)
axiosInstance.interceptors.response.use(
(response) => {
return response
},
async (error) => {
const originalRequest = error.config
if (error.response.status === 401 && !originalRequest._retry) {
originalRequest._retry = true
try {
const refreshToken = authStore.refreshToken
const response = await axios.post('/api/refresh-token', { refreshToken })
authStore.accessToken = response.data.accessToken
localStorage.setItem('accessToken', response.data.accessToken)
originalRequest.headers.Authorization = `Bearer ${response.data.accessToken}`
return axiosInstance(originalRequest)
} catch (error) {
authStore.logout()
return Promise.reject(error)
}
}
return Promise.reject(error)
}
)
export default axiosInstance
在上面的代码中,我们使用了Axios的拦截器来处理请求和响应。在请求拦截器中,我们为每个请求添加了Authorization
头,包含了当前的Access Token。在响应拦截器中,我们检查了响应的状态码,如果状态码为401(未授权),则尝试使用Refresh Token获取新的Access Token,并重新发送原始请求。
当Access Token过期时,服务器会返回401状态码。此时,我们需要使用Refresh Token来获取新的Access Token。如果Refresh Token也过期了,则需要用户重新登录。
通过上述逻辑,我们实现了无感刷新Token的功能。当Access Token过期时,系统会自动使用Refresh Token获取新的Access Token,并继续完成用户的请求,用户无需感知Token的刷新过程。
在实际应用中,可能会遇到多个请求同时发送的情况。如果多个请求都因为Access Token过期而返回401状态码,我们需要确保只有一个请求去刷新Token,其他请求等待刷新完成后再继续。
我们可以通过一个标志位来实现这个功能。
let isRefreshing = false
let failedQueue = []
const processQueue = (error, token = null) => {
failedQueue.forEach((prom) => {
if (error) {
prom.reject(error)
} else {
prom.resolve(token)
}
})
failedQueue = []
}
axiosInstance.interceptors.response.use(
(response) => {
return response
},
async (error) => {
const originalRequest = error.config
if (error.response.status === 401 && !originalRequest._retry) {
if (isRefreshing) {
return new Promise((resolve, reject) => {
failedQueue.push({ resolve, reject })
})
.then((token) => {
originalRequest.headers.Authorization = `Bearer ${token}`
return axiosInstance(originalRequest)
})
.catch((err) => {
return Promise.reject(err)
})
}
originalRequest._retry = true
isRefreshing = true
try {
const refreshToken = authStore.refreshToken
const response = await axios.post('/api/refresh-token', { refreshToken })
authStore.accessToken = response.data.accessToken
localStorage.setItem('accessToken', response.data.accessToken)
originalRequest.headers.Authorization = `Bearer ${response.data.accessToken}`
processQueue(null, response.data.accessToken)
return axiosInstance(originalRequest)
} catch (error) {
processQueue(error, null)
authStore.logout()
return Promise.reject(error)
} finally {
isRefreshing = false
}
}
return Promise.reject(error)
}
)
为了进一步提升用户体验,我们可以在Token即将过期时提前刷新Token,而不是等到Token过期后再刷新。这可以通过定时器来实现。
const refreshTokenBeforeExpiry = () => {
const expiryTime = 5 * 60 * 1000 // 5 minutes before expiry
setTimeout(async () => {
try {
const refreshToken = authStore.refreshToken
const response = await axios.post('/api/refresh-token', { refreshToken })
authStore.accessToken = response.data.accessToken
localStorage.setItem('accessToken', response.data.accessToken)
refreshTokenBeforeExpiry()
} catch (error) {
authStore.logout()
}
}, expiryTime)
}
// Call this function after login
refreshTokenBeforeExpiry()
在开发过程中,我们需要对双Token机制进行充分的测试,确保在各种情况下都能正常工作。可以使用Postman或浏览器开发者工具来模拟不同的场景,如Token过期、并发请求等。
通过本文的介绍,我们详细讲解了如何在Vue3和Vite项目中使用双Token机制实现无感刷新。双Token机制不仅提高了应用的安全性,还提升了用户体验。通过合理的配置和优化,我们可以确保应用在各种情况下都能正常工作。希望本文对你有所帮助,祝你在开发过程中顺利实现双Token机制!
免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。