为什么要使用双token
token是为了防止用户信息传来传去导致被劫持,但是假如token没有过期时间或者过期很长,那么显然token被劫持还是不安全的,token就失去了意义。
如果token过期时间设置的过短,这时就需要用户频繁的进行登录操作,对于用户体验不好
这时就需要双token来达到无痛刷新token的效果
具体实现:
设计图
具体逻辑
- 当用户登录成功后,后端会返回两个token,一个accessToken过期时间可能十分钟或二十分钟就会过期,一个是refreshToken过期时间有一周或两周时间
- 用户登录成功之后的请求,我会在axios的请求拦截器中将accessToken带到请求头中,如果accessToken没有过期就是正常的发送请求,一旦用户发送请求时accessToken过期了,后端会返回相应状态码‘401’
- 此时我们会在响应拦截器中拦截到这个‘401’状态码,这也就表示这accessToken过期了,这时我需要把请求头换成refreshToken,再次发送这个请求,去获取更新后的accessToken
- 此时会有两种情况,一个是refreshToken没过期,一个是refreshToken也过期了
- 如果refreshToken没过期,后端会返回响应状态码1024并返回更新后的accessToken,这时也会被响应拦截器拦截到,这时我就需要把更新后的accessToken带到请求头上再次发送这个请求,此时accessToken肯定没过期,请求正常发送
- 如果refreshToken过期了,后端会返回响应状态码1023,这时我就知道refreshToken也过期了,也就是用户登录过期了,就会让用户重新进行登录操作
自此就完成了token的无痛刷新,在用户访问网站时,用户对于accessToken的刷新是没有体感的,只会在一两周的时间间隔refreshToken也过期的时候进行一次登录操作,就可以正常访问网站了,即降低了用户实际accessToken被劫持的风险,又提升了用户的体验
实现代码:
let refreshToken = getToken('refreshToken') || ''
let isrefreshToken = false
if (!getToken('refreshToken')) {
isrefreshToken = false
if (!getToken('refreshToken')) {
isrefreshToken = true
}
}
// 添加请求拦截器
request.interceptors.request.use((config) => {
// 在请求之前做些什么(获取并设置token)
// 对双token的操作
let token = getToken('accessToken')
// 如果有token
if (token) {
// 并且token没过期
if (!isrefreshToken) {
config.headers['x-token'] = getToken('accessToken') || ''
}
}
// 如果有refreshToken
if (refreshToken) {
// 且需要刷新token
if (isrefreshToken) {
config.headers['x-token'] = getToken('refreshToken')
}
}
return config
}),
(error) => {
return Promise.reject(error)
}
// 添加响应拦截器
request.interceptors.response.use((response) => {
// 对响应数据做些什么
console.log('响应状态码', response.data.code)
// console.log('isrefreshToken', isrefreshToken);
let code = response.data.code
// 还没有设置refreshToken请求头,需要设置一下再次发送请求
if (!refreshToken && getToken('refreshToken') != null) {
refreshToken = getToken('refreshToken')
return request(response.config)
}
if (code == 401) {
//accessToken过期了,需要带着refreshToken,去换取新的token
refreshToken = getToken('refreshToken')
isrefreshToken = true
// 相当于重新走一遍刚刚的请求
return request(response.config)
}
if (code == 1024) {
setToken('accessToken', response.data.data)
isrefreshToken = false
return request(response.config)
} else if (code == 1023) {
// 将本地token删除
removeToken('refreshToken')
removeToken('accessToken')
// 跳转到登录页面,重新登录
router.push('/login')
//返回信息,让用户重新登录
isrefreshToken = true
alert('登录已超期,请重新登录')
}
return response
}),
(error) => {
return Promise.reject(error)
}