手牵手带你实现无痛刷新token!什么,uniapp中还不会,那也手拉手带你实现一下吧

文章介绍了token在用户认证中的作用,包括accessToken和refreshToken的使用,以及如何在axios中实现响应拦截器进行无痛刷新token。同时,讨论了axios的二次封装好处,并提供了在uniapp环境下处理微信小程序限制的网络请求库luch-request的使用示例,实现了无痛刷新token的功能。
摘要由CSDN通过智能技术生成

概述

点关注,不迷路!先点赞,后观看,养成阅读好习惯!你长得这么好看,点个赞不过分吧~

   无论是在web端的项目,还是在移动端,H5项目的开发过程中,为了保证请求的合法与安全性,
   经常会在请求时带上token(accessToken)。 那么,token是啥呢?
   简单来说,token就是用户登录成功后,后端生成的一个字符串序列,
   将这个序列返回给前端后,前端对其进行存储,并且在后续的所有请求中都必须携带上这个内容,
   以验证用户身份的合法性及保证安全性。token分为accessToken和refreshToken。
   accessToken是请求携带的内容,有效时间较短。
   refreshToken是用来刷新accessToken的,有效时间较长
   
   举个栗子:如果accessToken的有效期是一个小时,而refreshToken的有效期是七天。
   那么在我们登录过后一个小时以内,我们去请求任何接口都没有问题(假设你拥有所有接口的权限)。
   但是如果我们登录过后就将页面丢在那里了,过了一个小时过后才去请求其他的接口,
   那么请求就会不成功,因为后端会检测到我们的accessToken已过期,请求不是合法有效的了,
   就不会给我们返回内容,而是返回一个状态码,一般为401。
   此时,你就需要用有效的refreshToken去重新置换一个有效的accessToken,
   然后重新发起请求。如果不这样做呢?那用户只要使用你开发的应用,就必须每隔一个小时就登录一次。。。
   我相信你也不想开发出这么垃圾的项目吧!
   这样做的话,用户在refreshToken有效期内请求接口都是合法有效的, 提高了用户的体验。
   当然,刷新的操作也可以由后端完成,这样后端只用返给你一个accessToken,用于验证用户身份状态就行了,
   这就取决于你和后端谁能battle过谁了。。嘿嘿

手把手先带你实现一下axios的二次封装

有人可能会说axios已经很好用了,为什么还要对其进行二次封装?我一点都不否认axios的NB之处,但是试想一下,如果我们正在开发一个项目,网络请求采用axios实现,在我们项目开发完毕,即将上线时,axios团队突然解散了,那么axios也就不在维护了,碰巧这时axios出现了重大的bug,而你项目中的几百个甚至上千个接口全部采用axios直接发起的网络请求,这个时候再去重新换一个新的网络请求库,你想想有多爽!!!而对其进行二次封装后则不会出现这种状况了,这是我们只需把封装的axios库换成一个新的网络请求库就可以了,所有的请求均无需更改。啧啧啧啧啧
在这里插入图片描述

import axios from 'axios'
import { BASE_URL, TIMEOUT } from './config' 

// 说明
// BASE_URL:你自己项目的baseurl
// TIMEOUT: 取决于你自己想设置默认值为多少,一般为6000
// 这里只对其进行简单封装,如果想实现更复杂的封装,
// 或者你自己的项目有其他的要求,可自行扩充

class Request {
  constructor(baseURL, timeout = 6000) {
    this.instance = axios.create({
      baseURL,
      timeout
    })
  } 
  request(config) {
    return new Promise((resolve, reject) => {
      this.instance.request(config).then(res => {
        resolve(res)
      }).catch(err => {
        reject(err)
      })
    })
  }
  
  get(config) {
    return this.request({ ...config, method: "get" })
  }

  post(config) {
    return this.request({ ...config, method: "post" })
  }
  
  delete(config) {
    return this.request({ ...config, method: "delete" })
  }
    
  put(config) {
    return this.request({ ...config, method: "put" })
  }
}

export default new Request(BASE_URL, TIMEOUT) 

基于axios,在响应拦截器中实现无痛刷新token

import axios from 'axios'
import { BASE_URL, TIMEOUT } from './config' 

// 说明
// BASE_URL:你自己项目的baseurl
// TIMEOUT: 取决于你自己想设置默认值为多少,一般为6000
// 这里只对其进行简单封装,如果想实现更复杂的封装,
// 或者你自己的项目有其他的要求,可自行扩充

class Request {
  constructor(baseURL, timeout = 6000) {
    this.instance = axios.create({
      baseURL,
      timeout
    })

	// 请求拦截
    this.instance.interceptors.request.use(res => { 
      const accessToken = sessionStorage.getItem('accessToken')
      if (accessToken) {
        res.headers.authorization = accessToken
      } 
      return res
    }, err => {
      console.log("请求失败") 
      return err
    })
    // 响应拦截 
    this.instance.interceptors.response.use(response => { 
      console.log(response)
      const config = response.config
      const code = response.data.code 
      switch(code) {
        case 200:
          break;
        case 401: 
          const refreshToken = sessionStorage.getItem('refreshToken') 
          // 判断refreshToken是否有效,有效再去判断是否已经刷新,无效直接退出,重新登录
          if(refreshToken) {
              return axios.post({
                // 仅为示例,需替换为你自己项目中刷新token的接口
                url: baseURL + '/refreshToken',
                headers: {
                  authorization: refreshToken
                }
              }).then(res => {
                sessionStorage.removeItem('accessToken')
                newAccessToken = 'Bearer ' + res.data.accessToken
                sessionStorage.setItem('accessToken', newAccessToken) 
                // 刷新过后重新发起请求
                axios.request(config)
              })
            } else {
            // 提示登录过期,重新登录 
          }
      } 
      return response
    }, err => {
      console.log(err);
      console.log("响应失败") 
      return err
    })

  } 
  request(config) {
    return new Promise((resolve, reject) => {
      this.instance.request(config).then(res => {
        resolve(res)
      }).catch(err => {
        reject(err)
      })
    })
  }
  
  get(config) {
    return this.request({ ...config, method: "get" })
  }

  post(config) {
    return this.request({ ...config, method: "post" })
  }
  
  delete(config) {
    return this.request({ ...config, method: "delete" })
  }
    
  put(config) {
    return this.request({ ...config, method: "put" })
  }
}

export default new Request(BASE_URL, TIMEOUT) 

上面的代码有一些问题就是,accessToken过期了,如果同时发起的请求有多个,那么多个请求都会去刷新token,造成资源的浪费,同时增加了服务器的压力,因为可以设置一个标志位来优化一下。

优化1:防止重复刷新

import axios from 'axios'
import { BASE_URL, TIMEOUT } from './config' 

// 说明
// BASE_URL:你自己项目的baseurl
// TIMEOUT: 取决于你自己想设置默认值为多少,一般为6000
// 这里只对其进行简单封装,如果想实现更复杂的封装,
// 或者你自己的项目有其他的要求,可自行扩充

class Request {
  constructor(baseURL, timeout = 6000) {
    this.instance = axios.create({
      baseURL,
      timeout
    })

	// 请求拦截
    this.instance.interceptors.request.use(res => { 
      const accessToken = sessionStorage.getItem('accessToken')
      if (accessToken) {
        res.headers.authorization = accessToken
      } 
      return res
    }, err => {
      console.log("请求失败") 
      return err
    })
    
    /* 用来判断是否需要刷新token以及防止重复刷新token
      因为如果一次发起了多个请求,但是此时token已过期,需要刷新
      第一个请求将会去刷新token,但是刷新token也需要时间
      如果没有一个变量用来控制的话,其他的请求也会去再次刷新token,造成资源浪费
    */
    let isRefreshing = false  
    let newAccessToken = '' 
    // 响应拦截  
    this.instance.interceptors.response.use(response => { 
      console.log(response)
      const config = response.config
      const code = response.data.code 
      switch(code) {
        case 200:
          break;
        case 401: 
          const refreshToken = sessionStorage.getItem('refreshToken') 
          // 判断refreshToken是否有效,有效再去判断是否已经刷新,无效直接退出,重新登录
          if(refreshToken) {
            // 判断是否正在刷新token,如果没在刷新就去刷新
            if(!isRefreshing) {
              isRefreshing = true
              return axios.post({
                // 仅为示例,需替换为你自己项目中刷新token的接口
                url: baseURL + '/refreshToken',
                headers: {
                  authorization: refreshToken
                }
              }).then(res => {
                sessionStorage.removeItem('accessToken')
                newAccessToken = 'Bearer ' + res.data.accessToken
                sessionStorage.setItem('accessToken', newAccessToken)
                // 刷新成功后重新发起请求
                axios.request(config)
              }).finally(() => {
                // 刷新token后将标志位置为false
                isRefreshing = false 
              })
            }
          } else {
            // 提示登录过期,重新登录
            router.replace('/login')
          }
      } 
      return response
    }, err => {
      console.log(err);
      console.log("响应失败") 
      return err
    })

  } 
  request(config) {
    return new Promise((resolve, reject) => {
      this.instance.request(config).then(res => {
        resolve(res)
      }).catch(err => {
        reject(err)
      })
    })
  }
  
  get(config) {
    return this.request({ ...config, method: "get" })
  }

  post(config) {
    return this.request({ ...config, method: "post" })
  }
  
  delete(config) {
    return this.request({ ...config, method: "delete" })
  }
    
  put(config) {
    return this.request({ ...config, method: "put" })
  }
}

export default new Request(BASE_URL, TIMEOUT) 

上面代码还有一个问题就是,重新刷新accessToken过后,负责刷新的那个接口会重新发起请求,而同时打进来的其他请求就不会再次发起请求了。因此,需要用一个重试队列,保存发起的请求,等待token刷新后,再依次重新发起请求。

优化2:保证刷新后不丢失请求

```javascript
import axios from 'axios'
import { BASE_URL, TIMEOUT } from './config' 

// 说明
// BASE_URL:你自己项目的baseurl
// TIMEOUT: 取决于你自己想设置默认值为多少,一般为6000
// 这里只对其进行简单封装,如果想实现更复杂的封装,
// 或者你自己的项目有其他的要求,可自行扩充

class Request {
  constructor(baseURL, timeout = 6000) {
    this.instance = axios.create({
      baseURL,
      timeout
    })

	// 请求拦截
    this.instance.interceptors.request.use(res => { 
      const accessToken = sessionStorage.getItem('accessToken')
      if (accessToken) {
        res.headers.authorization = accessToken
      } 
      return res
    }, err => {
      console.log("请求失败") 
      return err
    })
   /* 用来判断是否需要刷新token以及防止重复刷新token
      因为如果一次发起了多个请求,但是此时token已过期,需要刷新
      第一个请求将会去刷新token,但是刷新token也需要时间
      如果没有一个变量用来控制的话,其他的请求也会去再次刷新token,造成资源浪费
    */
    let isRefreshing = false 
    /* 需要重新发起请求的队列,每一项将是一个待执行的函数形式
      如果一次发起了多个请求,但是此时token已过期,需要刷新
      刷新过后,还要重新发起请求,所以需要有一个队列来存储 
    */
    let requests = []
    let newAccessToken = '' 
    // 响应拦截 
    this.instance.interceptors.response.use(response => { 
      console.log(response)
      const config = response.config
      const code = response.data.code 
      switch(code) {
        case 200:
          break;
        case 401: 
          const refreshToken = sessionStorage.getItem('refreshToken') 
          // 判断refreshToken是否有效,有效再去判断是否已经刷新,无效直接退出,重新登录
          if(refreshToken) {
            // 判断是否正在刷新token,如果没在刷新就去刷新
            if(!isRefreshing) {
              isRefreshing = true
              return axios.post({
                // 仅为示例,需替换为你自己项目中刷新token的接口
                url: baseURL + '/refreshToken',
                headers: {
                  authorization: refreshToken
                }
              }).then(res => {
                sessionStorage.removeItem('accessToken')
                newAccessToken = 'Bearer ' + res.data.accessToken
                sessionStorage.setItem('accessToken', newAccessToken)
                axios.request(config)
                // 刷新成功后将请求队列里的请求依次执行
                requests.forEach(cb => cb(newAccessToken))
                requests = [] // 执行完毕后将请求队列置空
              }).finally(() => {
                // 刷新token后将标志位置为false
                isRefreshing = false 
              })
            } 
            // 已经在刷新了,就不用再刷新了。
            // 同时将发起的其他请求存储起来,等待刷新成功后重新发起请求
            else { 
              return new Promise(resolve => {
                requests.push(token => {
                  config.headers.authorization = token
                  resolve(axios.request(config))
                })
              })
            }
          } else {
            // 提示登录过期,重新登录 
          }
      } 
      return response
    }, err => {
      console.log(err);
      console.log("响应失败") 
      return err
    })

  } 
  request(config) {
    return new Promise((resolve, reject) => {
      this.instance.request(config).then(res => {
        resolve(res)
      }).catch(err => {
        reject(err)
      })
    })
  }
  
  get(config) {
    return this.request({ ...config, method: "get" })
  }

  post(config) {
    return this.request({ ...config, method: "post" })
  }
  
  delete(config) {
    return this.request({ ...config, method: "delete" })
  }
    
  put(config) {
    return this.request({ ...config, method: "put" })
  }
}

export default new Request(BASE_URL, TIMEOUT) 

手把手带你实现uniapp中的无痛刷新token

在uniapp中开发项目,然后部署到微信小程序,是不能直接使用axios的。因为axios 的底层是依赖于 XMLHttpRequest 函数来发起请求的,但是微信小程序禁用了 XMLHttpRequest 函数,仅开放了wx.request 来发起请求,所以说axios是没法直接在为微信小程序上面使用的,可以直接使用uniapp 官方封装的uni.request来发起请求。但是uni.request有个问题就是,它没有拦截器,那我们刷新token不就成了一个大麻烦了吗?别怕,这不是有我吗,来,手把手带你实现。uniapp官方的插件市场有一个不错的基于Promise,使用条件编译开发,专用于uni-app项目的网络请求库luch-request,我们基于这个库,就可以实现无痛刷新token了。思想代码逻辑都是一样的,这里简单实现一下:

import Request from './luch-request/luch-request/index.js'
export const apiBaseUrl = 'http://xxx/'
const request= new Request()


request.setConfig((config) => { /* 设置全局配置 */
  config.header = {
    ...config.header,
  }
  return config
})

// 请求拦截
request.interceptors.request.use((config) => { // 可使用async await 做异步操作
  config.baseURL = apiBaseUrl
  config.header = {
    ...config.header,
    Authorization: uni.getStorageSync('accessToken'), 
  }
  return config
}, err => { 
  return Promise.reject(err)
})
// 防止重复刷新标志位
let isRefreshing = false
// 重试队列,每一项将是一个待执行的函数形式
let requests = []

// 响应拦截 
request.interceptors.response.use((response) => { 
	const accessToken = uni.getStorageSync('accessToken')
	const refreshToken = uni.getStorageSync('refreshToken')
	if (response) {
		switch (response.statusCode) {
			case 200:
					break
			case 401:
				const config = response.config
				let newToken;
				if(!isRefreshing) {
					isRefreshing = true 
					return uni.request({  
						url: 'https://xxx/refreshToken',
						method: 'POST',
						header: {
							Authorization: refreshToken
						} 
					}).then(res => { 
						uni.removeStorageSync('accessToken')
						newToken = uni.setStorageSync('accessToken', 'Bearer ' + res.data.accessToken)
						return request.request(config)
						requests.forEach(cb => cb(newToken))
						requests = []
					}).finally(() => {
						isRefreshing = false
					})
				}
				else{
					return new Promise((resolve) => {
						requests.push((newToken) => {
							config.header.Authorization = newToken
							resolve(http.request(config))
						})
					})
				}
				return Promise.reject(response)
			default:
					break
		}
	}
	return response
}, err => {
	return Promise.reject(err)
})

export default request

  • 8
    点赞
  • 29
    收藏
    觉得还不错? 一键收藏
  • 6
    评论
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值