一、前言
JWT的Token登录认证是目前比较流行的跨域认证解决方案。原理在于将认证信息保存在客户端,下次访问其他页面时,需要从客户端传递认证信息回服务器端
具体实现如下:
1.登录成功,后端生成带有access_token并返回前端,前端将access_token保存到本地(cookie或者localStorage,cookie遇跨域问题可以设置withCredentials)
2.其他请求将使用access_token请求接口资源,后端access_token校验通过则调用接口成功;如果token超时,客户端携带refresh_token调用接口获取新的access_token。
3.后端接受刷新access_token的请求后,检查refresh_token是否过期。如过期,拒绝刷新,客户端收到该状态后,跳转到登录页;如无过期,客户端携带新的access_token重新调用上面的资源接口。
二、难点
1.同时有多个异步接口请求,当其中一个接口发现access_token过期,其他接口如何处理
三、解决方案
前端拦截响应数据,并发起刷新token的请求,拿到最新的token保存到本地,并去进行刚刚未请求成功的接口。在刷新token请求期间,其他异步请求将返回一个未执行resolve的promise,并把resolve
压入数组中等待执行,token刷新成功后遍历数组直接执行
四、代码实现
import { refreshToken } from '../apis/login' // 是否正在刷新的标记 let isRefreshing = false ; // 每一项将是一个待执行的函数形式 let requests = []; // response interceptor service.interceptors.response.use( async (response) => { const res = response.data switch (res.code) { case 4007: if (!isRefreshing) { isRefreshing = true //刷新token方法,等待刷新token方法执行返回后在执行后面的代码 const { data } = await refreshToken({ token: getToken( 'token' ) }) setToken( 'token' , data.token) /* 重新发送上一条请求(这条请求不会再下的else代码中拦截),await不需要加,无需异步完成以后在执行后面函数 */ requests.forEach(request => request()); requests = []; isRefreshing = false ; return service({ ...response.config }); } else { // 正在刷新token,将返回一个未执行resolve的promise // 拦截其他所有的请求,使其处于一个未执行的状态,只有当刷新token请求返回以后,再执行拦截的所有请求 return new Promise((resolve) => { // 将resolve放进数组,用一个函数形式来保存,等token刷新后直接执行 requests.push(() => { resolve(service({ ...response.config })) }) }) } } } ) export default service |
五、其他解决方案
经过思考和与后端同事的沟通后,发现在后端用“锁”也能实现类似功能,并且如果项目涉及多客户端,用后端实现可能会更优,后续也会和后端同事继续沟通和探讨。