vue 双Token 的无感刷新

何为双 token

- accessToken : 用户获取数据权限

- refreshToken : 用来获取新的accessToken

双 token 验证机制,其中 accessToken 过期时间较短,refreshToken 过期时间较长。当 accessToken 过期后,使用 refreshToken 去请求新的 token。

无感刷新:

当客户端检测到access token即将过期或已经过期时,自动在后台向认证服务器发起请求,携带refresh token换取新的access token。这个过程对用户来说是无感知的,即用户不需要重新登录,页面也不会中断或刷新,因此被称为“无感刷新”。

实现方式:

当 accessToken 过期时,调接口,会返回201,表示token过期,这个时候需要调用 refreshToken 接口,重新获取新的token,在这个期间执行的请求,都存放到一个新的请求队列中,当获取新的token之后再重新调用,接口返回202的时候表示 refreshToken 过期,需要给用户提示,重新登录页面,跳转到登录页

具体步骤:

1.先再store 中定义接口,存储 refreshToken 和 accessToken ,定义刷新token接口,修改 refreshToken 和 accessToken
import { login,refreshToken} from '@/api/login'

const user = {
  state: {
    access_token: getStore({
      name: 'access_token'
    }) || '',
    refresh_token: getStore({
      name: 'refresh_token'
    }) || '',
  },

  mutations: {
    SET_ACCESS_TOKEN(state, token) {
      state.access_token = token
      setStore({
        name: 'access_token',
        content: state.access_token,
        type: 'session'
      })
    },
    SET_REFRESH_TOKEN: (state, rfToken) => {
      state.refresh_token = rfToken
      setStore({
        name: 'refresh_token',
        content: state.refresh_token,
        type: 'session'
      })
    },
  },

  actions: {
    // 登录
    Login({ commit }, userInfo) {
      const loginname = userInfo.loginname.trim()
      const password = userInfo.password
      return new Promise((resolve, reject) => {
        login(loginname, password).then(res => {
          clearStore()
          clearStore({ type: 1 })
          commit('SET_ACCESS_TOKEN', res.data.accessToken)
          commit('SET_REFRESH_TOKEN', res.data.refreshToken)
          resolve()
        }).catch(error => {
          reject(error)
        })
      })
    },
    // 刷新token
    RefreshToken({ commit, state }) {
      return new Promise((resolve, reject) => {
        refreshToken({refreshToken:state.refresh_token}).then(res => {
          if(res.success){
            commit('SET_ACCESS_TOKEN', res.data.accessToken)
            commit('SET_REFRESH_TOKEN', res.data.refreshToken)
          } 
          resolve(res)
        }).catch(error => {
          reject(error)
        })
      })
    },
  }
}


export default user
2.再在请求拦截器中针对不同返回值来处理token情况
//1引入
import axios from 'axios'
import NProgress from 'nprogress' // progress bar
import { Message, MessageBox, Notification } from 'element-ui'
import store from '@/store'
import errorCode from '@/util/errorCode'
import 'nprogress/nprogress.css'
import { baseURL, buildEnv, env } from './baseURL'

axios.defaults.headers['Content-Type'] = 'application/json;charset=utf-8'
//2创建axios实例
let service = axios.create({
    baseURL: baseURL[buildEnv],
    timeout: 6000
})
// 返回其他状态吗
service.defaults.validateStatus = function (status) {
    return status >= 200 && status <= 500 // 默认的
}

// 跨域请求,允许保存cookie
service.defaults.withCredentials = true
// NProgress Configuration
NProgress.configure({
    showSpinner: false
})

//创建拦截器
service.interceptors.request.use(
    config => {
        NProgress.start()
        // 是否需要设置 token
        const isToken = (config.headers || {}).isToken === false
        const token = store.getters.access_token
        if (token && !isToken) {
            config.headers['Authorization'] = token// 让每个请求携带自定义token 
        }
        return config
    },
    error => {
        console.log(error);
        return Promise.reject(error);
    }
)

//标志当前是否正在刷新token
var isRefreshing = true
//请求队列
var requests = []
service.interceptors.response.use(
    response => {
        NProgress.done()
        let res = response.data;
        const config = response.config;
        if (!res.success) {
            // 未设置状态码则默认成功状态
            const code = Number(res.errorCode)
            // 获取错误信息
            const msg = res.errorMsg || errorCode[code] || errorCode.default['default']
            // 令牌无效 
            if (code === 200 || code === 202) {
                logout()
            } else if (code === 201) {
                //访问令牌过期
                if (isRefreshing) {
                    isRefreshing = false
                    store.dispatch('RefreshToken')
                        .then(res => {
                            if (res.success) {
                                // 执行失效函数
                                requests.forEach((cb) => cb())
                                //重新请求完清空
                                requests = []
                                return service(config)
                            }
                        })
                        .finally(() => {
                            isRefreshing = true;
                        });
                }
                // 返回未执行 resolve 的 Promise
                return new Promise(resolve => {
                    // 用函数形式将 resolve 存入,等待刷新后再执行
                    requests.push(() => {
                        resolve(service(config));
                    });
                });
            }
            else {
                Message({
                    message: msg,
                    type: 'error'
                })
                return Promise.resolve(res)
            }

        } 
        return Promise.resolve(res)
    },
    error => {
        NProgress.done()
        let { message } = error;
        Message({
            message: message,
            type: 'error',
            duration: 5 * 1000
        })
        return Promise.reject(error)
    }
)


// 退出
const logout = () => {
    MessageBox({
        message: '登录状态已过期,请重新登录',
        type: 'error',
        lockClickModal: false // 设置为false,点击弹窗外围不关闭弹窗
    }).then(() => {
        store.dispatch('LogOut').then(() => {
            store.commit('cleanMenu')
            // 刷新登录页面,避免多次弹框
            window.location.reload()
        })
    })
}
export default service


3.具体截图 页面 请求

在这里插入图片描述

Vue3中,实现token(例如JWT和OAuth)无感刷新通常涉及到服务端的配合以及前端的生命周期管理。以下是基本步骤: 1. **配置守卫**: - 使用axios拦截器,在请求头中自动添加这两个token。例如,可以创建两个interceptor,一个用于处理JWT,另一个用于OAuth。 ```javascript import axios from 'axios'; const jwtInterceptor = axios.interceptors.request.use( config => { // 添加JWT token if (localStorage.getItem('jwt')) { config.headers.Authorization = `Bearer ${localStorage.getItem('jwt')}`; } return config; }, error => Promise.reject(error) ); const oauthInterceptor = axios.interceptor.request.use( config => { // 添加OAuth token if (localStorage.getItem('oauthToken')) { config.headers.Authorization = `Bearer ${localStorage.getItem('oauthToken')}`; } return config; }, error => Promise.reject(error) ); // 注册拦截器 axios.use(jwtInterceptor); axios.use(oauthInterceptor); ``` 2. **服务端支持**: - 后端需要设置好无感刷新的机制,当一个token过期时,返回新的token同时附带旧token信息,前端可以通过这个信息更新当前的token。 3. **状态管理**: - 使用Vuex或pinia等状态管理库来保存用户的登录状态和token。当接收到新的token时,更新store状态,并触发视图层的数据更新。 4. **状态感知组件**: - 在组件中,使用计算属性或watch监听store中的登录状态变化,自动进行token切换和刷新操作。如果在某个地方检测到token无效,则尝试进行刷新。 5. **错误处理**: - 需要处理token失效时的用户反馈,比如显示友好的错误提示,引导用户重新授权。 注意:这只是一个基础示例,实际场景可能会更复杂,比如处理各种网络状况、权限验证等问题。此外,为了安全性考虑,建议在生产环境中对敏感操作进行严格的令牌验证。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值