vue axios中使用access_token、refresh_token实现接口保活的方案
回想起在做智能应用平台移动端时,通过jwt(JSON WEB TOKEN)的方式实现身份认证。简单来说就是一个加密令牌,服务端能通过算法加密用户信息并随着请求和相应传送,进而能让服务端知道登录的是谁,他有没有权限进入我的服务器获取信息。jwt有过期时间,过了这个时间,客户单再次向服务端发送接口请求的时候就需要进行重新认证,即重新登录。假如用户正在操作,这时候再次请求后端服务的时候就会突然跳到登录界面,严重影响了用户体验,智能应用平台jwt的失效时间是15min,先别说用户会有怎样的体验,就是在开发过程中,也需要反复请求新的token,简直要崩溃!还记得当时我们采用的方案是每隔一定时间,就静默地调用token请求接口,替换旧的token,这样避免用户在操作的过程中不会突然跳到登录界面的不好体验。但是,这样一来,就使得token失去了它应有的安全机制。基于当时技术的局限,只能采用这种方式。
最近看到了通过access_token、refresh_token实现token失效无痕刷新的技术博客,让我的思绪又回到了两年前那个激荡的日子。采用access_token过期,refresh_token进行刷新,很好的兼顾了用户体验和安全性的要求。基本思路如下:
- access_token为登录后获得的请求凭证,有效期较短,例如智能应用平台为15min,使用expires(过期时间)与当前时间戳进行比较,判断是否过期,若过期,需要使用refresh_token重新获取;
- refresh_token是用来刷新access_token的凭证,作为获取当前access_token的参数,换取新一轮的access_token,有效期一般为两倍的access_token,若过期,则需要重新登录;
- expires是当前access_token过期时刻的时间戳,一般会在距离过期前一定时间重新请求一一轮的access_token。
刷新access_token的机制
所谓的刷新access_token就是在用户登录后会返回access_token和refresh_token两个字段,用户在访问需要权限的接口时需要带access_token字段才能进行访问,也会返回access_token的过期时间,客户端在发起每次请求时都需要检测access_token是否即将过期,如果过期了,当用户访问权限接口时就会报错而退出。为了提高用户使用时的体验感,当access_token即将过期时,客户端需要用refresh_token主要发起刷新access_token的接口来获取新的access_token。刷新成功后会返回新的access_token和refresh_token,当然了,如果refresh_token也过期了的话就只能跳到登录界面了。
刷新access_token的实现
基于axios实现该方案,axios有请求拦截器和响应拦截器,可以在请求和响应之前对请求做一些处理来实现该方案。这里有两种场景需要讨论,第一种场景是一个页面可能存在多个请求,若刚好打开页面的时候access_token过期就会有多个刷新access_token的请求;第二种场景是,一个接口刷新access_token,新的access_token还没来得及更新,另外的接口就用老的access_token去请求数据也会出错。所以,采用以下方式:
- 当打开页面时access_token已经过期,这个页面所有请求通过数组的方式进行保存;
- 设置一个标志判断是否正在刷新,由一个接口去请求刷新access_token,待access_token更新后,再让其他请求用心的access_token重新请求数据;
关键代码
js
/** 是否正在刷新的标志 */
isRefreshing = false;
/** 存储请求的数组 */
let subscribers = [];
/** 将请求放入数组中 */
const addSubscribers = (cb) => subscribers.push(cb);
/** 带新的token并执行 */
const doSubscribers = (cb) => subscribers.map((cb) => cb(token));
/** axios拦截器 */
req.interceptors.request.use((request) => {
/** 判断是否登录 */
if (token) {
request.headers.token = token;
/** 判断是否即将过期,并且不是刷新token的接口refreshToken,这里是伪代码 */
if (isExpired() && request.url !== API.refreshToken) {
/** 先判断当前是否正在刷新,如果不是,将刷新标志置为true并请求心的token,如果是,将请求存到数组里 */
if (!isRefreshing) {
isRefreshing = true;
refreshtoken().then((res) => {
/** 新的token替换旧的token */
request.headers.token = res.data.token;
/** 跟新新token和refresh_token以及相关业务逻辑,这里是伪代码 */
setData();
/** 执行数组里的请求 */
doSubscribers();
})
}
let retry = new Promise((resolve, reject) => {
addSubscribers((token) => {
request.headers.token = token;
resolve(request)
});
});
return retry;
} else {
return request;
}
} else {
return request;
}
}, (error) => {
return promise.reject(error);
});
总结
使用access_token、refresh_token实现接口保活的方案不仅能够用于移动客户端,而且还能应用于采用jwt方案的鉴权系统,该方案在充分发挥jwt安全校验的基础上,还能极大的提高用户体验,并且,该方案中运用的请求队列的编程思想较为先进,对后续的开发有很好的借鉴和帮助作用。
接口数据测试
参数名称 | 参数位置 | 类型 | 必填 | 描述 | 示例 |
---|---|---|---|---|---|
access_token | Head | String | true | 用户凭证,获取方式参考文档(help.hikyun.com/document/16... | eyJ···9.eyJ···0.3cA···g |