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 过期,需要给用户提示,重新登录页面,跳转到登录页

具体实现:

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

再在请求拦截器中针对不同返回值来处理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


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值