前端实现token的无感刷新#记录

junsens 2024-07-15 16:03:02 阅读 84

因为服务器的token一版不会设置太长,token过期后就需要重新登录,频繁的登录会造成体验不好的问题,因此,需要体验好的话,就需要定时去刷新token,并替换之前的token。以下是token失效的效果:

那么做到token的无感刷新,主要有3种方案:

方案一:

后端返回过期时间,前端每次请求就判断token的过期时间,快到过期时间,就去调用刷新token接口。

缺点:若本地时间被篡改,特别是本地时间比服务器时间慢时,拦截会失败,不建议采用。

方案二:

后端返回过期时间,前端写个定时器,然后定时刷新token接口。

缺点:浪费资源,消耗性能,不建议采用。

方案三: 

在请求响应拦截器中拦截,判断token 返回过期后,调用刷新token接口,推荐使用。

那么以下是方案三的实现步骤:

1、首次登录的时候会获取到两个token,把获取到的两个token存到前端缓存中。

<code>localStorage.setItem('refreshToken',xxx)

localStorage.setItem('token', xxx)

2、token刷新基础写法

import axios from "axios"

let flag = false // 设置开关,防止多次请求

let expiredList = [] //存储过期的请求

// 把过期请求添加在数组中

function addRequest(request) {

expiredList.push(request)

}

// 创建axios实例

const service = axios.create({

baseURL: 'http://xxxxxxxxx', //后台请求接口路径

timeout: 10000 // 请求超时时间

})

service.interceptors.request.use(

config => {

localStorage.getItem('token') && (config.headers.Authorization = localStorage.getItem('token'))

config.headers['Content-Type'] = config.headers['Content-Type'] || 'application/json;charset=utf-8'

return config

},

error => {

Promise.reject(error)

}

)

service.interceptors.response.use(res => {

const code = res.data.code

if (code === 70001 || code === 401) { //与后端约定相应的code以此判断是否过期

// 把过期请求存储起来,用于请求到新的刷新token

addRequest(() => resolve(http(config)))

// 用刷新token去请求新的主token

if (!flag) {

flag = true;

// 获取刷新token

let refreshToken = localStorage.getItem('refreshToken');

if (refreshToken) {

// 判断刷新token是否过期, getRefreshToken这个是你封装的请求刷新token的接口

getRefreshToken(refreshToken).then(res => {

if (判断刷新token失效,退出登录) {

flag = false

// 移除刷新token

localStorage.removeItem('refreshToken')

localStorage.removeItem('token')

location.href = '/' //跳转到你指定的页面

} else if (res.code === 1000) {

flag = false

// 重新发送请求

expiredList.forEach((request) => request())

expiredList = []

}

})

}

}

}else if(code === 200){

return res.data

}

},

error => {

return Promise.reject(error)

}

)

export default service

3、token刷新进阶写法(包含防止多次刷新token、同时发起两个或者两个以上的请求时刷新token)

import axios from 'axios'

// 是否正在刷新的标记

let isRefreshing = false

//存储过期的请求

let expiredList = []

// 创建axios实例

const service = axios.create({

baseURL: 'http://xxxxxxxxx', //后台请求接口路径

timeout: 10000 // 请求超时时间

})

service.interceptors.request.use(

config => {

localStorage.getItem('token') && (config.headers.Authorization = localStorage.getItem('token'))

config.headers['Content-Type'] = config.headers['Content-Type'] || 'application/json;charset=utf-8'

return config

},

error => {

Promise.reject(error)

}

)

service.interceptors.response.use(

res => {

//约定code 401 token 过期

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

if (!isRefreshing) {

isRefreshing = true

//调用刷新token的接口

return refreshToken({ refreshToken: localStorage.getItem('refreshToken'), token: localStorage.getItem('token') }).then(res => {

const { token } = res.data

// 替换token

localStorage.setItem('token', token)

res.headers.Authorization = `${token}`

// token 刷新后将数组的方法重新执行

expiredList.forEach((cb) => cb(token))

expiredList = [] // 重新请求完清空

return service(res.config)

}).catch(err => {

localStorage.removeItem('refreshToken')

localStorage.removeItem('token')

location.href = '/' //跳转到你指定的页面

return Promise.reject(err)

}).finally(() => {

isRefreshing = false

})

} else {

// 返回未执行 resolve 的 Promise

return new Promise(resolve => {

// 用函数形式将 resolve 存入,等待刷新后再执行

expiredList.push(token => {

res.headers.Authorization = `${token}`

resolve(service(res.config))

})

})

}

}

return res && res.data

},

(error) => {

return Promise.reject(error)

}

)

4、上述写法是方便在此文章中理解无感刷新token的代码,其中储存token到本地缓存中的代码可以提取出来,然后在使用到的地方导入使用即可。

例子:把需要的方法提取出来

export function getRefreshTokenn() {

return localStorage.getItem(refreshToken)

}

export function setRefreshToken(token) {

return localStorage.setItem('refreshToken',token)

}

export function removeRefreshToken() {

return localStorage.removeItem('refreshToken')

}

使用方法:

import { getRefreshTokenn, setRefreshToken, removeRefreshToken } from '@/utils/auth'

把 localStorage.removeItem('refreshToken') 这段代码替换成 removeRefreshToken()



声明

本文内容仅代表作者观点,或转载于其他网站,本站不以此文作为商业用途
如有涉及侵权,请联系本站进行删除
转载本站原创文章,请注明来源及作者。