Vue3+Vite怎么使用双token实现无感刷新

发布时间:2023-04-25 14:29:06 作者:zzz
来源:亿速云 阅读:280

Vue3+Vite怎么使用双token实现无感刷新

目录

  1. 引言
  2. 什么是双Token机制
  3. 为什么需要双Token机制
  4. Vue3和Vite简介
  5. 项目初始化
  6. 配置Vite
  7. 安装依赖
  8. 创建登录页面
  9. 实现登录逻辑
  10. 双Token机制的实现
  11. 使用Axios拦截器
  12. 处理Token过期
  13. 无感刷新Token
  14. 处理并发请求
  15. 优化用户体验
  16. 测试与调试
  17. 常见问题与解决方案
  18. 总结

引言

在现代Web应用中,用户认证和授权是一个非常重要的部分。为了确保用户的安全性和数据的隐私性,通常会使用Token机制来进行身份验证。然而,传统的单Token机制存在一些问题,比如Token过期后用户需要重新登录,这会影响用户体验。为了解决这个问题,双Token机制应运而生。

本文将详细介绍如何在Vue3和Vite项目中使用双Token机制实现无感刷新,从而提升用户体验。

什么是双Token机制

双Token机制是指使用两个Token来进行用户认证和授权。通常,这两个Token分别是:

  1. Access Token(访问令牌):用于访问受保护的资源,有效期较短。
  2. Refresh Token(刷新令牌):用于在Access Token过期后获取新的Access Token,有效期较长。

通过使用双Token机制,可以在Access Token过期时,通过Refresh Token自动获取新的Access Token,从而实现无感刷新,避免用户频繁登录。

为什么需要双Token机制

  1. 提高安全性:Access Token的有效期较短,即使被泄露,攻击者也只能在短时间内使用它。而Refresh Token的有效期较长,但通常存储在更安全的地方(如HttpOnly Cookie),不易被窃取。
  2. 提升用户体验:用户无需频繁登录,即使Access Token过期,系统也能自动刷新Token,保持用户的登录状态。
  3. 减少服务器压力:通过Refresh Token获取新的Access Token,减少了频繁登录带来的服务器压力。

Vue3和Vite简介

Vue3

Vue3是Vue.js的最新版本,带来了许多新特性和改进,如Composition API、更好的TypeScript支持、性能优化等。Vue3的响应式系统更加高效,使得开发者能够更轻松地构建复杂的Web应用。

Vite

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的配置文件是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
  }
})

双Token机制的实现

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拦截器

在上面的代码中,我们使用了Axios的拦截器来处理请求和响应。在请求拦截器中,我们为每个请求添加了Authorization头,包含了当前的Access Token。在响应拦截器中,我们检查了响应的状态码,如果状态码为401(未授权),则尝试使用Refresh Token获取新的Access Token,并重新发送原始请求。

处理Token过期

当Access Token过期时,服务器会返回401状态码。此时,我们需要使用Refresh Token来获取新的Access Token。如果Refresh Token也过期了,则需要用户重新登录。

无感刷新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过期、并发请求等。

常见问题与解决方案

  1. Refresh Token过期:如果Refresh Token也过期了,用户需要重新登录。可以通过提示用户重新登录来解决。
  2. 并发请求问题:多个请求同时触发Token刷新时,需要确保只有一个请求去刷新Token,其他请求等待刷新完成后再继续。可以通过标志位和队列来解决。
  3. 安全性问题:确保Refresh Token存储在安全的地方,如HttpOnly Cookie,避免被XSS攻击窃取。

总结

通过本文的介绍,我们详细讲解了如何在Vue3和Vite项目中使用双Token机制实现无感刷新。双Token机制不仅提高了应用的安全性,还提升了用户体验。通过合理的配置和优化,我们可以确保应用在各种情况下都能正常工作。希望本文对你有所帮助,祝你在开发过程中顺利实现双Token机制!

推荐阅读:
  1. vue3.0新特性是什么
  2. 如何用40行代码把Vue3的响应式集成进React做状态管理

免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。

vue3 token vite

上一篇:怎么使用VUE实现一键复制内容功能

下一篇:vue在组件内部data是一个函数而不是一个对象的原因是什么

相关阅读

您好,登录后才能下订单哦!

密码登录
登录注册
其他方式登录
点击 登录注册 即表示同意《亿速云用户服务条款》