因为服务器的token一版不会设置太长,token过期后就需要重新登录,频繁的登录会造成体验不好的问题,因此,需要体验好的话,就需要定时去刷新token,并替换之前的token。以下是token失效的效果:
那么做到token的无感刷新,主要有3种方案:
方案一:
后端返回过期时间,前端每次请求就判断token的过期时间,快到过期时间,就去调用刷新token接口。
缺点:若本地时间被篡改,特别是本地时间比服务器时间慢时,拦截会失败,不建议采用。
方案二:
后端返回过期时间,前端写个定时器,然后定时刷新token接口。
缺点:浪费资源,消耗性能,不建议采用。
方案三:
在请求响应拦截器中拦截,判断token 返回过期后,调用刷新token接口,推荐使用。
那么以下是方案三的实现步骤:
1、首次登录的时候会获取到两个token,把获取到的两个token存到前端缓存中。
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以此判断是否过期 // 把过期请求存储起来,用于请求到新的刷新tokenaddRequest(() => resolve(service(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()