vue的token刷新处理
以token处理登录的web系统,一般会有两个token:access-token和refresh-token。node.js中,一般用jsonwebtoken这个模块。access-token,是用户输入登录的账号密码,后台去db验证然后颁发的,它一般记录在浏览器的cookie中,并在浏览器关闭时自动删除,页面访问或ajax访问会自动通过cookie传回到后台,后台直接内存中校验,不用访问db,
前言
以token处理登录的web系统,一般会有两个token:access-token和refresh-token。
node.js中,一般用jsonwebtoken这个模块。
access-token,是用户输入登录的账号密码,后台去db验证然后颁发的,它一般记录在浏览器的cookie中,并在浏览器关闭时自动删除,页面访问或ajax访问会自动通过cookie传回到后台,后台直接内存中校验,不用访问db,所以效率高;为了在access-token泄漏后及时止损,一般access-token会设置一个有效期,如1-8小时。
access-token设置了有效期后,过期了怎么办?为了及时止损,有效期不能设置太长,过期是一定会遇到的,比如工作狂,如果有效期设置的是8小时,他开着浏览器工作12小时,费力断断续续花了1个小时(电话多,喝咖啡尿多)打了张订单,提交时token过期了。再比如某些大屏幕展示的页面,可能连续几天几月的开着。遇到过期怎么办?
- 重定向到登录页面,用户输入账号密码登录后,再自动跳回订单页面,之前的资料都丢了,用户骂一句“靠”忍气吞声重新打。如果每天遇到一次,可能还可以忍。如果有效期太短,如1小时,每天遇到5,6次,那用户可能不干了,这时你可能要把未提交的订单资料暂时存到localstorage里面。
- 弹出登录框。登录框内容和代码如何做,预先就加载了,每个页面都有这部分,感觉很浪费,因为大部分时间用不上,动态从后台加载,可能不好实现。
- 登录后把账号密码记录在浏览器中,自动登录。但基于安全考虑,一般是用户特别勾选“记住我”,才会加密记录账号密码到localstorage中,用户下次打开浏览器时自动登录。如果token过期就自动登录,如何及时止损?后台修改密码或禁用账号,如何同步到前端的localstorage中。大部分app是这么干的。以上3种都是要再次去后台数据库验证,所以token过期时间不能太短,否则效率很差。
- 设置refresh-token机制,颁发access-token时同时颁发一个refresh-token,唯一区别是refresh-token有效期比较长,比如1个月。当access-token过期后,拿着refresh-token到后台换取新的access-token。通过在后台为refresh-token设置黑名单来及时止损,所以有黑名单的时候,可能效率也会一样的差。refresh-token也过期后,那就只有老老实实的让用户输入账号密码登录了,就是前面的1,2方法。因为refresh-token不常用,所以最好不要放在cookie中避免每次自动传到后台,放在localstorage较好。
刷新access-token过程,如何让用户没感觉?思路是:发现access-token,自动用refresh-token去刷新,然后再自动跳到原来页面或者自动调用一次原来的ajax。
token身份验证机制
客户端登录请求成功后,服务器将用户信息(如用户id)使用特殊算法加密后作为验证的标志发送给用户(即token),当用户下次发起请求时,会将这个token捎带过来,服务器再将这个token通过解密后进行验证,通过的话,则向客户端返回请求的数据;反之,则请求失败。
token优点
它是无状态的,且服务器不用像传统的身份认证(session)那样需要保存会话信息,减轻了服务器的压力。
vue的token刷新处理
在对token身份验证机制进行一次简单介绍后,进入正文…
一般为了安全性,token都会设置一个过期时间,在过期之后就无法请求相关接口了,这时应该怎么办呢,是直接退出登录吗?
在目前公司的项目里,为了更好的用户体验,我们选择手动刷新token。登录请求成功后,会返回一个token和token过期时间,在每次请求api时,前端可以先判断一下token是否即将过期或已过期,如果是,则请求刷新token的接口,成功替换原来的token之后才可以重新发起请求。
下面,我们直接看代码,这是在vue的请求拦截器里进行的相关操作:
/*是否有请求正在刷新token*/
window.isRefreshing = false;
/*被挂起的请求数组*/
let refreshSubscribers = [];
/*获取刷新token请求的token*/
function getRefreshToken() {
return JSON.parse(localStorage.auth).refresh_token;
}
/*push所有请求到数组中*/
function subscribeTokenRefresh(cb) {
refreshSubscribers.push(cb);
}
/*刷新请求(refreshSubscribers数组中的请求得到新的token之后会自执行,用新的token去请求数据)*/
function onRrefreshed(token) {
refreshSubscribers.map(cb => cb(token));
}
/*请求拦截器*/
ajax.interceptors.request.use(
config => {
const authTmp = localStorage.auth;
/*判断是否已登录*/
if (authTmp) {
/*解析登录信息*/
let auth = JSON.parse(authTmp);
/*判断auth是否存在*/
if (auth) {
/*在请求头中添加token类型、token*/
config.headers.Authorization = auth.token_type + " " + auth.token;
/*判断刷新token请求的refresh_token是否过期*/
if (util.isRefreshTokenExpired()) {
alert("刷新token过期,请重新登录");
/*清除本地保存的auth*/
localStorage.removeItem("auth");
window.location.href = "#/login";
return;
}
/*判断token是否将要过期*/
if (
util.isTokenExpired() &&
config.url.indexOf("admin/auth/current") === -1
) {
/*判断是否正在刷新*/
if (!window.isRefreshing) {
/*将刷新token的标志置为true*/
window.isRefreshing = true;
/*发起刷新token的请求*/
apiList
.refreshToken({ refresh_token: getRefreshToken() })
.then(res => {
/*将标志置为false*/
window.isRefreshing = false;
/*成功刷新token*/
config.headers.Authorization =
res.data.data.token_type + " " + res.data.data.token;
/*更新auth*/
localStorage.setItem(
"auth",
JSON.stringify(res.data.data)
);
/*执行数组里的函数,重新发起被挂起的请求*/
onRrefreshed(res.data.data.token);
/*执行onRefreshed函数后清空数组中保存的请求*/
refreshSubscribers = [];
})
.catch(err => {
alert(err.response.data.message);
/*清除本地保存的auth*/
// localStorage.removeItem('auth')
window.location.href = "#/login";
});
}
/*把请求(token)=>{....}都push到一个数组中*/
let retry = new Promise((resolve, reject) => {
/*(token) => {...}这个函数就是回调函数*/
subscribeTokenRefresh(token => {
config.headers.Authorization = "Bearer " + token;
/*将请求挂起*/
resolve(config);
});
});
return retry;
}
}
return config;
} else {
/*未登录直接返回配置信息*/
return config;
}
},
/*错误操作*/
err => {
return Promise.reject(err);
}
);
这里需要注意几点:
1、当token即将过期或者已过期时,原则上,我们只需要有一个接口去触发刷新token的请求即可,这里的isRefreshing 变量,就起到这样一个监控的作用,它相当于一把锁,当刷新token的操作被触发后,其他的触发操作就被排斥在外了。
window.isRefreshing = false
2、刷新token的接口,用到了一个另外的token(refresh_token),这也是出于安全性考虑的,并且它也有过期时间,不过这个过期时间一般都比普通token的过期时间要长,所以在上面代码中,会发现,我在请求拦截中优先判断了refresh_token是否过期,如果过期则直接退出登录,不再进行下一步的操作。
/*判断刷新token请求的refresh_token是否过期*/
if (util.isRefreshTokenExpired() && config.url.indexOf('admin/auth/current') === -1) {
alert('刷新token过期,请重新登录')
/*清除本地保存的auth*/
localStorage.removeItem('auth')
window.location.href = '#/login'
return
}
3、在触发了刷新token的操作后,我们还需要先将其他的请求挂起,在获取新的token之后再重新发起这些请求。
/*把请求(token)=>{....}都push到一个数组中*/
let retry = new Promise((resolve, reject) => {
/*(token) => {...}这个函数就是回调函数*/
subscribeTokenRefresh((token) => {
config.headers.Authorization = 'Bearer ' + token
/*将请求挂起*/
resolve(config)
})
})
return retry
在刷新token请求的成功回调里执行下面代码,重新发起请求。
/*执行数组里的函数,重新发起被挂起的请求*/
onRrefreshed(res.data.data.token)
4、因为有人在评论里问util文件,应该是想知道具体怎么判断token过期的,其实在获得token时,是有返回一个token过期时间 ,你可以先将它先保存起来,然后在需要时,拿出来与本地时间比较即可
/*判断token是否过期*/
function isTokenExpired() {
/*从localStorage中取出token过期时间*/
let expiredTime = new Date(JSON.parse(localStorage.auth).expired_at).getTime() / 1000
/*获取本地时间*/
let nowTime = new Date().getTime() / 1000
/*获取校验时间差*/
let diffTime = JSON.parse(sessionStorage.diffTime)
/*校验本地时间*/
nowTime -= diffTime
/*如果 < 10分钟,则说明即将过期*/
return (expiredTime - nowTime) < 10*60
}
更多推荐
所有评论(0)