前端JWT token维护方案

一、需求描述:

前端登录后,后端返回acToken和acToken有效时间以及refreshToken,然后在request.headers带上acToken,当acToken过期时要用refreshToken去获取新的acToken,当refreshToken过期前端跳转到登录页,获取新的acToken时要做到用户无感知。

 二、分析

当用户发起一个请求时,判断acToken是否已过期,若已过期则先调refreshToken接口,拿到新的acToken后再继续执行之前的请求。

这个问题的难点在于:

1、当同时发起多个请求,而刷新acToken的接口还没返回,此时其他请求该如何处理?

2、假如请求中带有用户行为,比如一个购买按钮,点击它会先发起用户是否被禁用接口,如果没有禁用,则跳转到购买页,否则弹窗提示。

三、方案

1、在请求发起前拦截每个请求,判断acToken的有效时间是否已经过期,若已过期,则将请求挂起,先刷新acToken后再继续请求。

  • 优点: 在请求前拦截,能节省请求,省流量。
  • 缺点: 需要后端额外提供一个acToken过期时间的字段;使用了本地时间判断,若本地时间被篡改,特别是本地时间比服务器时间慢时,拦截会失败。
PS:acToken有效时间建议是时间段,类似缓存的MaxAge,而不要是绝对时间。当服务器和本地时间不一致时,绝对时间会有问题。

2、不在请求前拦截,而是拦截返回后的数据。先发起请求,接口返回过期后,先刷新acToken,再进行一次重试。

  • 优点:不需额外的acToken过期字段,不需判断时间。
  • 缺点: 1、会消耗多一次请求,耗流量 2、请求then之后有用户行为,无法执行。
     

因为方案2没有解决到难点2,故此使用方案1,如有办法再研究

 

四、实现代码如下:

let isRefreshing = false
let requests =[]
function getJwtTokenVO(){
    return localStorage.getItem('jwtTokenVO')?JSON.parse(localStorage.getItem('jwtTokenVO')):''
}
// request interceptor
service.interceptors.request.use(
    config => {
        // 过滤掉刷新token接口(避免死循环!!!!)和登录接口
  if (config.url.indexOf('/refreshToken') >= 0 || config.url.indexOf('/login') >= 0) {
            return config
        }
        const jwtTokenVO = getJwtTokenVO()
        if (jwtTokenVO) {
            config.headers={...config.headers,acToken:jwtTokenVO.accessToken}
            const now = new Date().getTime()
            const accessTokenExpire = jwtTokenVO.accessTokenExpire //这个是绝对时间戳
  // 稳妥要提前一点时间刷新token
  if (now > (accessTokenExpire-5000)) {
                // 立即刷新token
  if (!isRefreshing) {
                    console.log('正在刷新token')
                    isRefreshing = true
  fetchRefreshToken({ token: jwtTokenVO.refreshToken }).then(resData => {
                        // 刷新token接口的时候可能引起refreshToken过期跳转登录页,这里没有进service.interceptors.response
  if (resData.code * 1 === 5004) {
                            localStorage.removeItem('jwtTokenVO')
                            let href = window.location.href
  /* refreshToken失效 这个code需要再修改 */
  window.location.href = `${location.origin}${location.pathname}${location.search}#/login?orgId=${localStorage.getItem('lc_orgId')}&next=${href}`
  return ''
  }
                        if (resData.code * 1 === 1) {
                            console.log('重新获取accessToken 成功')
                            /* 重新赋值accessToken */
  localStorage.setItem('jwtTokenVO', JSON.stringify(resData.data))
                            axios.defaults.headers['acToken'] = resData.data.accessToken
  }
                        isRefreshing = false
 return resData.data.accessToken
  }).then((accessToken) => {
                        console.log('刷新token成功,执行请求队列')
                        if(accessToken){
                            requests.forEach(cb => cb(accessToken))
                            // 执行完成后,清空队列
  requests = []
                        }
                    }).catch(res => {
                        console.error('刷新token error: ', res)
                    })
                }
                const requestList = new Promise((resolve) => {
                    requests.push((accessToken) => {
                        // 刷新config的旧token
  config.headers['acToken'] = accessToken
                        resolve(config)
                    })
                })
                return requestList
                //
  }
        }
        return config
    },
  error => {
        console.log(error) // for debug
  return Promise.reject(error)
    }
)
// 请求返回后拦截
instance.interceptors.response.use(response => {
    const { code } = response.data
  if (code === 5004) {
        localStorage.removeItem('jwtTokenVO')
        let href = window.location.href
  /* refreshToken失效 这个code需要再修改 */
  window.location.href = `${location.origin}${location.pathname}${location.search}#/login?orgId=${localStorage.getItem('lc_orgId')}&next=${href}`
  }
    return response
}, error => {
    console.log('catch', error)
    return Promise.reject(error)
})

参考链接:https://segmentfault.com/a/1190000020986592?utm_source=tag-newest

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值