一、什么是无感刷新token?
无感知刷新Token是指,在Token过期之前,系统自动使用Refresh Token获取新的Access Token,从而实现Token的无感知刷新,用户可以无缝继续使用应用。
在实现无感知刷新Token的过程中,需要考虑以下几个方面:
- 如何判断Token是否过期?
- 如何在Token过期时自动使用Refresh Token获取新的Access Token?
下面将介绍如何实现无感知刷新Token的具体步骤。
实现步骤
步骤一:获取AccessToken和RefreshToken。
在认证成功之后,我们需要将后端生成的AccessToken和RefreshToken发送给客户端。并将返回的token保存到本地缓存。AccessToken用于访问受保护的API,RefreshToken用于获取新的AccessToken。我们可以使用JWT(jsonwebtoken)来实现认证。
我们在后端设置生成token以及验证token是否过期的方法。
const jwt = require('jsonwebtoken')
let encryption= 'encryption'
const createToken = {
// 生成token
getToken(jiamiData,expiresIn){
return jwt.sign({
data:jiamiData,
},encryption,{expiresIn:expiresIn})
},
verify(token){
try{
return jwt.verify(token,encryption)
}catch(error){
return false
}
}
}
module.exports = createToken
然后在我们进行认证的时候调用生成token的方法getToken()。
let accessToken = createToken.getToken('user','5s');
let refreshToken = createToken.getToken('user','10s');
步骤二:设置请求拦截器,在请求中携带AccessToken。
在这里呢,我们就需要对axios进行二次封装,然后设置请求拦截器,在请求拦截器中给每个需要认证的API的请求头中携带AccessToken。AccessToken可以在我们本地缓存中拿到。如下所示:
// 进行axios二次封装:使用请求和响应拦截器
import axios from 'axios'
import { ElMessage } from 'element-plus'
// 第一步:利用axios对象的create方法,去创建axios实例(其他配置:基础路径、超时的时间)
const request = axios.create({
// 基础路径
baseURL: 'http://localhost:3000', //基础路径上会携带/api
timeout: 5000, //超时时间的设置
})
// 第二步:request实例添加请求与响应拦截器
request.interceptors.request.use(
(config) => {
// cnofig配置对象,headers属性请求头,经常给服务器携带公共参数
//返回配置对象
// 在发送请求之前做些什么
if (localStorage.getItem('AccessToken')) {
config.headers.Authorization =
'Bearer ' +
JSON.parse(JSON.stringify(localStorage.getItem('AccessToken'))) || ''
}
// 返回配置对象
return config
},
(error) => {
// 对请求错误做些什么
return Promise.reject(error)
},
)
步骤三:通过AccessToken向后端请求受保护数据。
在向后端请求数据的时候,后端会先获取到AccessToken进行判断是否过期,如果没有过期就会正常返回用户数据,如果过期了就会返回401状态码,并提示AccessToken已经过期了,无法返回用户数据。如果我们本地缓存中不存在AccessToken就会返回406状态码,并提示无效的刷新令牌。
router.get("/user", async (req, res) => {
const username = req.query.username;
const authHeader = req.headers.authorization;
if (authHeader) {
const token = authHeader.split(" ")[1]; // 提取Bearer JWT token
// 在这里可以对token进行验证和解码等操作
// 然后在后续的处理中使用解码后的信息
if (createTokenCheck.verify(token)) {
let data = await usersModel.find({ username });
res.send({
code: 200,
data,
});
} else {
res.send({
code: 401,
data: "token已过期",
});
}
} else {
res.send({
code: 406,
msg: "无效的刷新令牌",
});
}
});
步骤四:设置相应拦截器,拦截后端返回的信息并判断是否需要刷新RefreshToken。
在相应拦截器中,会拦截到服务器返回的数据,首先会判断是否是有效的AccessToken,如果不是就需要用户去重新进行认证,如果是有效的AccessToken就回去判断它是否过期,如果没有过期就会把数据返回给用户,如果过期了就会去调用refresh接口,将本地缓存中的RefreshToken作为参数传递给后端,在后端会去判断RefreshToken是否过期如果过期就需要用户重新认证,如果没有过期就会给客户端重新返回一组新生成的AccessToken和RefreshToken,并重新保存到本地缓存,将已经过期的AccessToken刷新,再次向后端发送一次请求,获取用户数据。
// 第三步:响应拦截器
request.interceptors.response.use(
async (response) => {
// 结构一层数据
const dataAxios = response
if (
dataAxios.data.msg == '刷新令牌已过期' ||
dataAxios.data.msg == '无效的刷新令牌'
) {
ElMessage.error('登录凭证过期,请重新登陆') //错误信息
localStorage.clear() // 删除缓存
isRefreshing = false // 请求成功,开启刷新标识
requests = [] // 置空
router.push('/login')
return false
}
// 无感刷新token
if (dataAxios.data.code === 401) {
if (!isRefreshing) {
isRefreshing = true
// 获取本地缓存中的RefreshToken
const RefreshToken = localStorage.getItem('RefreshToken')
// 请求新的token
const res = await refreshToken(RefreshToken)
// 请求成功,开启刷新标识
isRefreshing = false
localStorage.setItem('AccessToken', res.accessToken)
localStorage.setItem('RefreshToken', res.refreshToken)
// 已经刷新了token,将所有队列中的请求进行重试
requests.forEach((item) => item(res?.accessToken))
requests = []
}
return new Promise((resolve) => {
// 将resolve放进队列,用一个函数形式来保存,等token刷新后直接执行
requests.push((token) => {
response.config.headers.Authorization = `Bearer ${token}`
resolve(request(response.config)) //执行请求,
})
})
}
// 错误信息
if (!dataAxios.data && dataAxios.data.code != 0) {
ElMessage.error(dataAxios.data.msg)
}
return dataAxios.data
}
)
步骤五:刷新RefreshToken的接口设置。
router.get("/refresh", async (req, res) => {
const token = req.query.token;
let accessToken = createTokenCheck.getToken("user", "10s");
let refreshToken = createTokenCheck.getToken("user", "60s");
if (createTokenCheck.verify(token)) {
res.send({
code: 200,
refreshToken,
accessToken,
});
} else {
console.log("refresh");
res.send({
code: 401,
msg: "刷新令牌已过期",
});
}
});
二、无感刷新token的优点
用户体验改善:无感刷新Token技术可以在用户无感知的情况下,自动刷新访问令牌,避免了用户在过期后需要重新登录的操作,提高了用户体验。
安全性提升:通过无感刷新Token技术,可以减少因为Token过期而导致的用户访问权限问题。同时,在访问令牌过期后,通过自动刷新获取新的访问令牌,避免了客户端传输和保存用户凭证(如用户名和密码)的风险。
降低服务端压力:相比于每次请求都需要服务端检查访问令牌的有效性,无感刷新Token技术将刷新操作从服务端转移到了客户端,减轻了服务端的负担,提高了服务端的并发处理能力。
三、应用场景
Web应用或移动应用中:在Web应用或移动应用中,通过无感刷新Token技术,可以实现用户登录后,自动刷新访问令牌,使用户无需在过期后重新登录。
单页应用(SPA)中:在单页应用中,页面无刷新,因此无法通过传统方式进行Token刷新。通过无感刷新Token技术,可以在后台自动刷新访问令牌,保持用户会话的持续性。
使用长时间访问令牌的应用:有些应用使用长时间访问令牌(比如30天),为了保证安全性,需要定期刷新访问令牌。无感刷新Token技术可以方便地实现这个刷新过程。
多终端登录应用:在多终端登录的应用中,如电子商务平台或社交媒体,用户可能会在不同的设备上同时登录。通过无感刷新Token技术,可以保持用户在各个设备上的登录状态同步。
四、总结
无感刷新Token技术是一种在客户端应用中自动刷新访问令牌的机制,它通过提供用户无感知的刷新操作,改善了用户体验,提高了安全性,同时减轻了服务端的负担。这项技术适用于各种Web应用、移动应用和单页应用等场景,特别适用于需要长时间访问令牌和多终端登录的应用。通过无感刷新Token技术,我们能够在保证用户会话持续性的同时,提供更好的安全性和便利性。