vue3 token 无感刷新

实现原理

登录时 后端会返回 两个 token
    token:短期有效的临时凭证,正常请求时使用。
    refreshToken:长期有效的凭证(几天到几个月),token 过期后通过 refreshToken 重新获取新 token。

请求拦截器:负责给请求头设置对应的 token。
响应拦截器(重点):每一次响应时,响应头会携带 token返回,通过 localStorage 或 pinia 等进行全局存储。

无感刷新:当客户端请求时发现token过期,自动向服务器获取新的token,无需重新登录和页面刷新。

封装 axios

// request.js

import axios from "axios";
import { useUserStore } from '@/stores'
import { apiToken } from "@/api/api";

const instance = axios.create({
    // todo 1.基础地址,超时时间
    baseURL: '',
    timeout: 10000
})

// 请求拦截器
instance.interceptors.request.use(
    (config) => {
        const userStore = useUserStore()

        // 通过请求体自定义参数 __isRefreshToken 判断 传递 token 还是 refreshToken
        config.headers['Authorization'] = !config.__isRefreshToken ? `Bearer ${userStore.token}` : `Bearer ${userStore.refreshToken}`

        return config
    },
    (err) => Promise.reject(err)
)


// 响应拦截器
instance.interceptors.response.use(async (res) => {
    const userStore = useUserStore()

    // 判断 响应头 是否又返回 token 值
    if (res.headers.Authorization) {
        // 提取 token 值,全局保存和给请求头赋予最新 token 值
        const token = res.headers.Authorization.replace('Bearer ', '')
        userStore.setToken(token)
    }
    // 判断 响应头 是否又返回 refreshtoken 值
    if (res.headers.refreshtoken) {
        // 提取 refreshtoken 值并全局保存
        const refreshtoken = res.headers.Authorization.replace('Bearer ', '')
        userStore.setRefreshToken(refreshtoken)
    }

    // 状态码401:token 过期 且不是刷新 token 的请求
    if (res.data.code === 401 && !res.config.__isRefreshToken) {
        const tokenStatus = await refreshToken()
        if (tokenStatus) {
            // 重新赋值 token
            res.config.headers.Authorization = `Bearer ${userStore.token}`
            // 重新请求
            const resp = await instance.request(res.config)
            return resp
        } else {
            // 错误的特色情况 ==> 401 权限不足 或 token 过期 =>拦截到登录页面
            console.log('重新登录');
            // userStore.delToken()
            // userStore.delRefreshToken()
            return res.data
        }
    }

    // 正常返回数据
    return res.data
})

export default instance


// 通过 refreshToken函数 刷新 token
let promise
function refreshToken() {
    // promise 防止多次请求和并发请求时连续刷新 token
    if (promise) return promise
    promise = new Promise(async (resolve) => {
        // 刷新 token
        const res = await apiToken()
        // 返回是否刷新成功
        resolve(res.code === 200)
    })
    promise.finally(() => {
        promise = null // 清空 promise
    })
    return promise
}

 api.js

// api.js

import request from '@/utils/request'

// 登录
export function apiLogin() {
    return request.get(`/api/login`)
}

// 获取信息
export function apiInfo() {
    return request.get(`/api/info`)
}

// 刷新token
export function apiToken() {
    return request.get(`/api/token`, {
        __isRefreshToken: true
    })
}

pinia 

import { ref } from 'vue'
import { defineStore } from 'pinia'

// 用户数据
export const useUserStore = defineStore(
  'user',
  () => {
    const token = ref('')
    const refreshToken = ref('')
    // 设置 token
    const setToken = (access_token) => {
      token.value = access_token
    }
    //设置刷新token
    const setRefreshToken = (refresh_token) => {
      refreshToken.value = refresh_token
    }
    // 删除Token
    const delToken = () => {
      token.value = ''
    }
    // 删除Token
    const delRefreshToken = () => {
      refreshToken.value = ''
    }

    return {
      token,
      refreshToken,
      setRefreshToken,
      setToken,
      delRefreshToken,
      delToken
    }
  }
)

页面

<script setup>
import { apiLogin,apiInfo } from "@/api/api";

const login =async ()=>{
  await apiLogin()
  console.log('登录了');
  
}
const getInfo =async ()=>{  
 const res = await apiInfo()
 console.log(res.msg); 
}
</script>

<template>
  <div class="btn" @click="login">登录</div>
  <div class="btn" @click="getInfo">登录后获取信息</div>
</template>

<style scoped lang="scss">
  .btn {
    width: 200px;
    height: 40px;
    display: flex;
    justify-content: center;
    align-items: center;
    margin-bottom: 20px;
    background: skyblue;
    color: #fff;
    border-radius: 20px;
  }
</style>

Vue中实现无感刷新token的方法可以通过拦截器来实现。首先,你可以在axios的响应拦截器中判断返回的状态码是否为401,如果是的话,说明token已过期。然后,你可以设置一个变量isRefreshing来控制是否正在刷新token的状态。当第一个请求触发token过期时,isRefreshing会被设置为true,然后发送刷新token的请求,获取最新的token并进行覆盖。在刷新token的过程中,其他请求会被拦截并等待token刷新完成后再继续发送。当token刷新完成后,isRefreshing会被设置为false,其他请求会继续发送。以下是一个示例代码: ```javascript import axios from 'axios' let isRefreshing = false axios.interceptors.response.use( response => { if (response.status === 401) { if (!isRefreshing) { isRefreshing = true return refreshToken().then(res => { const { token } = res.data // 更新token response.headers.Authorization = `${token}` // 继续发送之前被拦截的请求 return axios(response.config) }).catch(err => { // 刷新token失败,跳转到登录页 router.push('/login') return Promise.reject(err) }).finally(() => { isRefreshing = false }) } } return response && response.data }, error => { // 处理错误 return Promise.reject(error) } ) ``` 在上述代码中,refreshToken()是一个发送刷新token请求的函数,你可以根据你的实际情况进行实现。当token过期时,其他请求会被拦截并等待token刷新完成后再继续发送。这样就实现了Vue中的无感刷新token的功能。\[1\]\[2\]\[3\] #### 引用[.reference_title] - *1* [VUE中拦截请求并无感刷新token](https://blog.csdn.net/Wangyuan_wo/article/details/121209540)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^koosearch_v1,239^v3^insert_chatgpt"}} ] [.reference_item] - *2* [Vue 无感刷新token](https://blog.csdn.net/weixin_45308405/article/details/127643153)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^koosearch_v1,239^v3^insert_chatgpt"}} ] [.reference_item] - *3* [VUE前端实现token无感刷新,即refresh_token](https://blog.csdn.net/yu1431/article/details/130835868)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^koosearch_v1,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值