在vue项目中实现Token替换和请求拦截

        Token在计算机身份认证中是令牌(临时)的意思,在词法分析中是标记的意思。一般作为邀请、登录系统使用。

        在vue项目中实现Token替换----前端学海

        这里使用的vue中的请求拦截器,根据后端返回的Token的生成时间,以及该Token的有效期和当前发送请求的时间进行计算,判断当前Token是否快要过期,以确定是否开始请求新的Token。

计算公式为:Token有效时间 -(当前时间 - Token的生成时间)= 剩余的Token有效时间

        这里我的判断为,Token剩余有效时间,小于五分钟的时候,调用刷新Token的接口,请求最新的Token。

在请求新Token期间,可能同时会有好几个请求同时发起,我们在这里将这些请求拦截下来,通过Promise,将这些请求,完整的防在待执行的请求队列中,

当新的Token请求成功,更新本地的Token之后,开始执行拦截的请求队列,并将这些请求的header中的Token替换为最新的Token,完成Token整个流程。

如果请求新Token接口失败,并且原Token已过了有效期时,提示登录过期,重定向到登录页,重新登录!

        如有建议,欢迎在评论区指出!!!

请看代码

import axios from 'axios' // 引入axios

import loginApi from './api/article/loginApi'

import router from '../router'

import qs from 'qs'

import {

  Message

} from 'element-ui'

console.info(location.hostname)

if (location.hostname === 'localhost' ||

  location.hostname === '127.0.0.1') {

  axios.defaults.baseURL = '/api/yifd'

} else {

  axios.defaults.baseURL = `https://${location.hostname}/proxy`

}

// eslint-disable-next-line no-unused-vars

/* 是否正在刷新的标志 */

window.isRefreshing = false

// token是否过期,默认为否

window.tokenOverdue = false

/* 存储请求的数组 */

const refreshSubscribers = []

/* 将所有的请求都push到数组中,其实数组是[function(token){}, function(token){},...] */

function subscribeTokenRefresh (cb) {

  refreshSubscribers.push(cb)

}

/* 数组中的请求得到新的token之后自执行,用新的token去请求数据 */

function onRrefreshed (token) {

  refreshSubscribers.map(cb => cb(token))

}

function tokenIsOverdue () { // token是否过期

  const time = window.sessionStorage.getItem('tokenTime') // token有效期,秒级,后端返回,浏览器存储的

  const queryTime = parseInt(window.sessionStorage.getItem('tokenQueryTime')) // token获取的时间,秒级

  const nowTime = new Date().getTime()

  const arealyTime = Math.ceil(nowTime / 1000) - queryTime // 已经过去的时间

  // console.log(time, queryTime, Math.ceil(nowTime / 1000), arealyTime)

  if ((time - arealyTime) < 300) { // token五分钟内即将过期时开启替换token,目前后台的token有效期为30分钟。

    return true

  } else {

    return false

  }

}

tokenIsOverdue()

/**

* 请求失败后的错误统一处理

* @param {Number} status 请求失败的状态码

*/

const errorHandle = (response) => {

  const status = response.data.code

  const msg = response.data.msg

  // 状态码判断

  switch (status) {

    // 401:未登录状态或token过期,跳转登录页

    case 401:

      Message.error(msg)

      break

    case 403:

      alert(msg)

      break

      // 404:请求不存在

    case 404:

      router.push({

        path: '/404'

      })

      Message.error('请求的资源不在')

      break

      // 服务器错误

    case 500:

      Message.error(msg)

      break

    default:

      console.log(msg)

  }

}

// 创建axios实例

var instance = axios.create({

  timeout: 1000 * 100

})

/**

* 请求拦截器

* 每次请求前,如果存在token则在请求头中携带token

*/

instance.interceptors.request.use(

  request => {

    // // 该位置会获取登陆成功时的token数据

    const token = window.sessionStorage.getItem('token')

    /* 判断用户是否已经登录 */

    if (token) {

      /* 请求头添加token信息 */

      request.headers.Authorization = token

      if (request.url.includes('ossUpload')) {

        request.timeout = 600000

      } else {

        request.timeout = 100000

      }

      /* 判断token是否即将过期 */

      /*  `oauth/token`是刷新token的接口,只有当token将要过期且不是请求刷新token的接口才会进入 */

      if (tokenIsOverdue() && !request.url.includes('oauth/token')) {

        /* 首先所有的请求来了,我们要先判断当前是否正在刷新,如果不是,将刷新的标志置为true并请求刷新token;如果是,将请求存储到数组中 */

        if (!window.isRefreshing) {

          window.isRefreshing = true

          loginApi.apiLogin(

            qs.stringify({

              grant_type: 'refresh_token',

              refresh_token: window.sessionStorage.getItem('refresh'),

              client_id: 'trade',

              client_secret: 'homedone'

            })

          ).then(res => {

            if (res.data.code === 200) {

              /* 将刷新的token替代老的token */

              request.headers.Authorization = 'Bearer ' + res.data.data.access_token

              /* 更新内存的token信息 */

              const resData = res.data.data

              window.sessionStorage.setItem('token', 'Bearer' + ' ' + resData.access_token) // 存入token Bearer

              window.sessionStorage.setItem('refresh', resData.refresh_token) // 存入刷新token,用来替换过期token

              window.sessionStorage.setItem('tokenTime', res.data.data.expires_in) // token有效期,秒级

              window.sessionStorage.setItem('tokenQueryTime', Math.floor(res.data.timestamp / 1000)) // token获取时间,秒级

              /* 执行数组里的请求,重新发起被挂起的请求 */

              onRrefreshed(res.data.data.access_token)

            } else { // 目前做的处理,当替换token请求失败时

              if (res.status === 401) { // 当刷新token时为401,标识refresh_token也已经过期,需重新登录

                Message.warning('登陆过期请重新登陆!')

                window.location = '#/' // 重定向回登录页

                window.sessionStorage.clear() // 清除包括token的所有缓存,让用户重新登录

              } else {

                window.isRefreshing = true

              }

            }

          })

          const retry = new Promise((resolve, reject) => {

            subscribeTokenRefresh((token) => {

              request.headers.Authorization = 'Bearer ' + token

              /* 将请求挂起 */

              resolve(request)

            })

          })

          return retry

        }

      } else {

        return request

      }

    } else {

      /* 如果没有登录直接返回请求 */

      return request

    }

    return request

  },

  error => {

    return Promise.reject(error)

  }

)

// 响应拦截器

instance.interceptors.response.use(

  // 请求成功

  res => {

    res.status === 200 ? Promise.resolve(res) : Promise.reject(res)

    return res

  },

  // 请求失败

  error => {

    const {

      response

    } = error

    if (response) {

      // 请求已发出,但是不在2xx的范围

      errorHandle(response)

      return Promise.reject(response)

    } else {

      return Promise.reject(error)

    }

  }

)

export default instance

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,012评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,628评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,653评论 0 350
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,485评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,574评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,590评论 1 293
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,596评论 3 414
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,340评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,794评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,102评论 2 330
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,276评论 1 344
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,940评论 5 339
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,583评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,201评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,441评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,173评论 2 366
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,136评论 2 352