1.该问题出现的原因
在前后端分离项目中,最常见的是前端点击登录后,后端返回token字符串,这个token可以看作是一个“令牌”,就比如你去酒店办理入住后,拿到的房卡,那代表你有着进去房间的权限。
1. 登录用户的token过期
token是具有时效性的,生活中,如你登录腾讯视频,接下来几天你再登录就不用输入账号密码,而时间很长如半个月后,你还要重新输入账号密码登录,这个过程就是token过期。
具体多久过期,一般是和后端商量着来,后台管理类项目如企业内部的项目为了安全性且并不会过多的考虑用户体验,一般设为很短或者干脆不做这个。
而像腾讯视频,淘宝,京东之类的商业类项目需要考虑用户体验,且对于安全性要求并不高,一般设置为7~14天比较合适。
2. 用户未登录情况,返回401错误,应该回到登录页(这个不一定是401错误)
3.小结:很普遍的功能,80%的项目都会做这个功能
2. 处理401问题的解决方案原理
完整的逻辑为:
前端请求接口api --> 返回401错误 --> 前端判断是否有refresh_token -->如果有就用refresh_token请求新的token --> 后台成功返回一个新的token给我们 --> 更新vuex+本地存储持久化 --> 然后重新发送请求 --> 带上新的token请求数据
当然,如果没有refresh_token就老老实实去登录吧!
方案: 目前常见的处理方式是:当用户登陆成功之后,返回的token中有两个值
原理:一个是token,他的有效期是2小时(举例),一个是姑且称为refresh_token,他的有效期长,比如是14天,假设用户登录后2小时后,token过期了,那么我们看一下refresh_token在不在,在的话,就用refresh_token再次发送,后端会返回一个新的token。
核心点:1.解决401问题重点在于让用户“无感”,也就是说用户不知道token过期也不需要用户再次登录,需要的是我们程序员去处理。
2.解决这个问题的地方在响应拦截器
3.使用响应拦截器解决问题
3.1 作用
所有从后端回来的响应都会集中进入响应拦截器中,如果发生401错误就可以解决
以下是我封装的响应拦截器(可以通用),主要完成两件事:
处理401问题,以及注入token
import router from '../router/auth.js' // 响应拦截器 request.interceptors.response.use(function (response) { console.log('响应拦截器', response) return response }, async function (error) { // 如果发生了错误,判断是否是401 console.dir(error) if (error.response.status === 401) { // 出现401就在这里面 开始处理 --- console.log('响应拦截器-错误-401') const refreshToken = store.state.tokenInfo.refresh_token // if (有refresh_token) { ---- 有refresh_token if (refreshToken) { // 1. 请求新token try { const res = await axios({ url: 'http://localhost:8000/v1_0/authorizations', method: 'PUT', headers: { Authorization: `Bearer ${refreshToken}` } }) console.log('请求新token', res.data.data.token) // 2. 保存到vuex store.commit('mSetToken', { // mSetToken是前面定义的mutations名字 refresh_token: refreshToken, token: res.data.data.token }) // 3. 重发请求 // request是上面创建的axios的实例,它会自动从vuex取出token带上 return request(error.config) } catch (error) { // 1. 清除token store.commit('mSetToken', {}) // 2. 去到登录页(如果有token值,就不能到login) const backtoUrl = encodeURIComponent(router.currentRoute.fullPath) router.push('/login?backto=' + backtoUrl) return Promise.reject(error) } } else { // 如果没有refresh_token的时候 ----没有refresh_token // 1.去到登录页 // 2.清除token store.commit('mSetToken', {}) const backtoUrl = encodeURIComponent(router.currentRoute.fullPath) // 回到原来跳过来的的页面,不加?后面的一串就会到首页 router.push('/login?backto=' + backtoUrl) return Promise.reject(error) // 返回错误信息 } } else { return Promise.reject(error) } })