注册

token过期了怎么办?

token过期了怎么办?一般做法是重复第一次获取token的过程(比如登录,扫描授权等) ,这样做的缺点是用户体验不好,每一小时强制登录一次几乎是无法忍受的。那应该怎么办呢?其实这是一个老生常谈的问题,但是最近发现很多人并不清楚,所以今天就一次讲清这个问题!

token 过期处理

没有绝对的安全, 所谓的安全处理, 就是提高攻击者攻击的难度, 对他造成了一定的麻烦, 我们这个网站就是安全的! 网站安全性就是高的! 所以: token 必须要有过期时间!

token 过期问题

目标: 了解token过期问题的存在, 学习token过期的解决思路

现象:

你登陆成功之后,接口会返回一个token值,这个值在后续请求时带上(就像是开门钥匙)。

但是,这个值一般会有有效期(具体是多长,是由后端决定),在我们的项目中,这个有效期是2小时。

如果,上午8点登陆成功,到了10:01分,则token就会失效,再去发请求时,就会报401错误。

思考:

  1. token需要过期时间吗 ?

    token即是获取受保护资源的凭证,当然必须有过期时间。否则一次登录便可永久使用,认证功能就失去了其意义。非但必须有个过期时间,而且过期时间还不能太长,

    参考各个主流网站的token过期时间,一般1小时左右

    token一旦过期, 一定要处理, 不处理, 用户没法进行一些需要授权页面的使用了

  2. token过期该怎么办?

    token过期,就要重新获取。

    那么重新获取有两种方式,一是重复第一次获取token的过程(比如登录,扫描授权等) ,这样做的缺点是用户体验不好,每一小时强制登录一次几乎是无法忍受的。

    那么还剩第二种方法,那就是主动去刷新token. 主动刷新token的凭证是refresh token,也是加密字符串,并且和token是相关联的。相比可以获取各种资源的token,refresh token的作用仅仅是获取新的token,因此其作用和安全性要求都大为降低,所以其过期时间也可以设置得长一些。

目标效果 - 保证每一小时, 都是一个不同的token

第一次请求 9:00 用的是 token1  第二次请求 12:00 用的是 token2

当用户登陆成功之后,返回的token中有两个值,说明如下:

image.png

  • token:

    • 作用:在访问一些接口时,需要传入token,就是它。
    • 有效期:2小时。
  • refresh_token

    • 作用: 当token的有效期过了之后,可以使用它去请求一个特殊接口(这个接口也是后端指定的,明确需要传入refresh_token),并返回一个新的token回来(有效期还是2小时),以替换过期的那个token。
    • 有效期:14天。(最理想的情况下,一次登陆可以持续14天。)

image.png

对于 某次请求A 的响应,如果是401错误

  • 有refresh_token,用refresh_token去请求回新的token

    • 新token请求成功

      • 更新本地token
      • 再发一次请求A
    • 新token请求失败

      • 清空vuex中的token
      • 携带请求地址,跳转到登陆页
  • 没有refresh_token

    • 清空vuex中的token
    • 携带请求地址,跳转到登陆页

对于一个请求的响应 401, 要这么处理, 对于十个请求的响应 401, 也要这么处理,

我们可以统一将这个token过期处理放在响应拦截器中

请求拦截器: 所有的请求, 在真正被发送出去之前, 都会先经过请求拦截器 (可以携带token)

响应拦截器: 所有的响应, 在真正被(.then.catch await)处理之前, 都会先经过响应拦截器, 可以在这个响应拦截器中统一对响应做判断

响应拦截器处理token

目标: 通过 axios 响应拦截器来处理 token 过期的问题

响应拦截器: http://www.kancloud.cn/yunye/axios…

  1. 没有 refresh_token 拦截到登录页, 清除无效的token

测试: {"token":"123.123.123"}

// 添加响应拦截器
http.interceptors.response.use(function (response) {
// 对响应数据做点什么 (成功响应) response 就是成功的响应 res
return response
}, function (error) {
// 对响应错误做点什么 (失败响应) 处理401错误
// console.dir(error)
if (error.response.status === 401) {
console.log('token过期了, 一小时过去了, 需要通过refresh_token去刷新token')
// 获取 refresh_token, 判断是否存在, 存在就去刷新token
const refreshToken = store.state.tokenInfo.refresh_token
if (refreshToken) {
console.log('存在refreshToken, 需要进行刷新token操作')
} else {
// 没有refreshToken, 直接去登录, 将来还能跳回来
// router.currentRoute 指向当前路由信息对象 === 等价于之前页面中用的 this.$route
// 清除本地token, 跳转登录 (无意义的本地token内容, 要清除)
store.commit('removeToken')
router.push({
path: '/login',
query: {
backto: router.currentRoute.fullPath
}
})
}
}
return Promise.reject(error)
})

提供清除token的mutation

// 移出tokenInfo的信息, 恢复成空对象
removeToken (state) {
state.tokenInfo = {}
// 更新到本地, 本地可以清掉token信息
removeToken()
},
  1. 有 refresh_token 发送请求, 刷新token

测试操作: 将 token 修改成 xyz, 模拟 token 过期, 而有 refresh_token 发现401, 会自动帮你刷新token

{"refresh_token":"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE2MDYzNTcyODcsInVzZXJfaWQiOjExMDI0OTA1MjI4Mjk3MTc1MDQsInJlZnJlc2giOnRydWV9.2A81gpjxP_wWOjclv0fzSh1wzNm6lNy0iXM5G5l7TQ4","token":"xyz"}

const refreshToken = store.state.tokenInfo.refresh_token
if (refreshToken) {
console.log('存在refreshToken, 需要进行刷新token操作')
// (1) 发送请求, 进行刷新token操作, 获取新的token
// 注意: 这边发请求, 不用http实例, 用它会自动在请求前帮你携带token(会覆盖你的refresh_token)
// 这边, 直接用 axios 发送请求
const res = await axios({
method: 'put',
url: 'http://ttapi.research.itcast.cn/app/v1_0/authorizations',
// 请求头中携带refresh_token信息
headers: {
Authorization: `Bearer ${refreshToken}`
}
})
const newToken = res.data.data.token
// (2) 将新token更新到vuex中
store.commit('setTokenInfo', {
refresh_token: refreshToken,
token: newToken
})
}
  1. 刷新token后, 应该重新发送刚才的请求 (让用户刷新token无感知)
return http(error.config)
  1. 那万一 refresh_token 也过期了, 是真正的用户登录过期了 (一定要让用户重新登录的)

测试: {"refresh_token":"123.123","token":"123.123.123"} 修改后, 修改的是本地, 记得刷新一下

从哪拦走的, 就回到哪去

// 添加响应拦截器
http.interceptors.response.use(function (response) {
// 对响应数据做点什么 (成功响应) response 就是成功的响应 res
return response
}, async function (error) {
// 对响应错误做点什么 (失败响应) 处理401错误
// console.dir(error)
if (error.response.status === 401) {
console.log('token过期了, 一小时过去了, 需要通过refresh_token去刷新token')
// 获取 refresh_token, 判断是否存在, 存在就去刷新token
const refreshToken = store.state.tokenInfo.refresh_token
if (refreshToken) {
try {
console.log('存在refreshToken, 需要进行刷新token操作')
// (1) 发送请求, 进行刷新token操作, 获取新的token
// 注意: 这边发请求, 不用http实例, 用它会自动在请求前帮你携带token(会覆盖你的refresh_token)
// 这边, 直接用 axios 发送请求
const res = await axios({
method: 'put',
url: 'http://ttapi.research.itcast.cn/app/v1_0/authorizations',
// 请求头中携带refresh_token信息
headers: {
Authorization: `Bearer ${refreshToken}`
}
})
const newToken = res.data.data.token
// (2) 将新token更新到vuex中
store.commit('setTokenInfo', {
refresh_token: refreshToken,
token: newToken
})
// (3) 重新发送刚才的请求, http, 自动携带token (携带的是新token)
// error.config就是之前用于请求的配置对象, 可以直接给http使用
return http(error.config)
} catch {
// refresh_token 过期了, 跳转到登录页
// 清除过期的token对象
store.commit('removeToken')
// 跳转到登录页, 跳转完, 将来跳回来
router.push({
path: '/login',
query: {
backto: router.currentRoute.fullPath
}
})
}
} else {
// 没有refreshToken, 直接去登录, 将来还能跳回来
// router.currentRoute 指向当前路由信息对象 === 等价于之前页面中用的 this.$route
// 清除本地token, 跳转登录 (无意义的本地token内容, 要清除)
store.commit('removeToken')
router.push({
path: '/login',
query: {
backto: router.currentRoute.fullPath
}
})
}
}
return Promise.reject(error)
})

注意点:

  1. 响应拦截器要加在axios实例 http 上。
  2. 用refresh_token请求新token时,要用axios,不要用实例 http (需要: 手动用 refresh_token 请求)
  3. 得到新token之后,再发请求时,要用 http 实例 (用token请求)
  4. 过期的 token 可以用 refresh_token 再次更新获取新token, 但是过期的 refresh_token 就应该从清除了


作者:JoyZ
来源:juejin.cn/post/7308992811449172005

0 个评论

要回复文章请先登录注册