欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

refreshToken多次请求问题

程序员文章站 2022-05-19 08:56:46
...

鉴权:

  • 由于本项目中使用了token+refreshToken进行鉴权,token用于身份的校验,而refreshToken用于请求得到新的token。token设置时长为2小时,refreshToken使用一次即更新。
  • token时效设为2小时主要是为了预防外部获取到用户的token,进行长时间的登录。因为在token过期前用户都可以使用token向后端获取相关的信息,一旦过期就不能通过后端的身份校验。
  • refreshToken的作用是保持用户的登录态,不至于每过2小时就需要重新登录。在用户每次请求数据的时候判断token是否过期,如果过期会向后端请求新的token,此时必须携带旧的token。这样后端可以判定该旧token是否是其生成的token,提升安全性。

数据存放:

token和refreshToken由于需要验证和请求所以放在本地缓存中,但实际的使用是使用vuex中的token和refreshToken(内存中);这是因为如果用户在登录期间删除了本地缓存不会影响用户的使用,如果只使用本地缓存存储token和refreshToken。那么在每次读取时都会被本地缓存是否存在该数据影响。

  • vuex中:
    当项目开始运行时会自动读取本地缓存中的数据,在运行期间直接读取内存中token和refreshToken中的数据。
    tokenExpirationTime默认为0,其值为设置token时间+过期时间的时间戳,该值用于和当前时间的时间戳做对比,如果当前时间大于该值,说明token过期。需要通过refreshToken重新请求token。重新刷新或直接访问页面时token处于过期状态。需要重新请求token。
//vuex中
const state = {
  refreshToken: getRefreshToken() || "",
  token: getToken() || "",
  tokenExpirationTime: 0
}
  • 本地缓存中:
    使用localstorage,我使用了插件store,分别用于获取,设置和移除token和refreshToken
var store = require("store");

const RefreshToken = "RefreshToken";
const TokenKey = "TokenKey";

export function getToken() {
  return store.get(TokenKey);
}

export function setToken(token) {
  return store.set(TokenKey, token);
}

export function removeToken() {
  return store.remove(TokenKey);
}

export function setRefreshToken(rToken) {
  return store.set(RefreshToken, rToken);
}

export function getRefreshToken() {
  return store.get(RefreshToken);
}

export function removeRefreshToken() {
  return store.remove(RefreshToken);
}

请求:

请求使用的是axios,用请求拦截器来判断token是否过期

请求拦截器


axios.interceptors.request.use(
  async function (config) {
      //获取token
      const token = store.state.user.token
      
      
      
      //不需要进行token更新和判断。refresh只需要过期token即可
      if (config.url === '/login' || config.url === '/refresh') {
      	//设置默认请求头
      	config.headers.Authorization = token || 'Bearer '
        return config
        // axios(config) 可以再次发送请求
      }
      
      //判断是否超时,如果超时更新token,如果没超时那么直接访问
      isTokenOverTime() && isRefreshTokenOption().catch((err) => {
      //由于如果在refreshToken请求期间,有新的请求来,那么需要限制其不能执行更新方法
        console.log(err)
      });
      
      //设置新的token
      config.headers.Authorization = store.state.user.token
      return config
    },
    function (error) {
      return Promise.reject(error)
    }
)

判断token是否过期

export function isTokenOverTime() {
  //获取token过期时间
  const tokenDelay = store.state.user.tokenExpirationTime;
  
  //获取当前时间
  const current = getTimeStamp();
  
  //判断是否过期
  return (current > tokenDelay)
}

查看是否锁住方法


//用于查看当前refreshToken是否被锁住,如果正在请求refreshToken,那么就被锁住,就不在执行新的refreshToken请求
let isRefreshTokenLock = false;

function checkLock() {
  if (isRefreshTokenLock) {
  	// 抛出异常 由isRefreshTokenOption方法捕获
    throw new Error("refreshToken is Lock");
  } else {
    isRefreshTokenLock = true;
  }
}

刷新token


// 用于刷新Token,如果有就直接执行,如果没有就需要跳转到登录页
export function isRefreshTokenOption() {
  return new Promise((resolve, reject) => {
    let user = store.state.user
    // 只有当vuex中有refreshToken和token才能进行请求,不然无法请求(请求参数)
    if (user.rToken && user.token) {
	
      try {
        checkLock();
      } catch (ex) {
        //捕获异常,说明当前refreshToken正在请求刷新token不需要再次请求,再抛出promise错误
        //让请求拦截器中的isRefreshTokenOption方法捕获。这样不会抛出红色错误。
        reject('error:' + ex.message)
        
        // 使用return 而不单单是reject。可以让代码终止在这里,不继续执行。
        //如果只使用reject,那么还会继续执行下面的请求。这不是我们想要的。
        return
      }
      
      //请求刷新token
      store.dispatch("user/refreshToken").then(() => {
        isRefreshTokenLock = false;
        resolve()
      });
    } else {
    //如果没有token或refreshToken就初始化token,refreshToken,tokenExpirationTime等,回到登录状态。
      store.commit('user/DEL_TOKEN')
      router.push('/login')
      location.reload() // 为了重新实例化vue-router对象 避免bug
      resolve()
    }
  })
}

总结:当请求拦截器发现token需要更新的时候。通过锁来锁住请求的方法,让它不能再次被请求。而由于当不需要被再次请求的时候,不需要执行完全部的代码,可以使用try…catch() { return } 来阻止代码继续向下运行。