无感知刷新Token示例简析
作者:XinD
引言
在前后端分离的应用中,使用Token进行认证是一种较为常见的方式。但是,由于Token的有效期限制,需要不断刷新Token,否则会导致用户认证失败。为了解决这个问题,可以实现无感知刷新Token的功能,本文将介绍如何实现无感知刷新Token。
Token认证的原理
在Web应用中,常见的Token认证方式有基于Cookie和基于Token的认证。基于Cookie的认证方式是将认证信息保存在Cookie中,每次请求时将Cookie发送给服务器进行认证;而基于Token的认证方式是将认证信息保存在Token中,每次请求时将Token发送给服务器进行认证。
在基于Token的认证方式中,客户端将认证信息保存在Token中,而不是保存在Cookie中。在认证成功后,服务器将生成一个Access Token和一个Refresh Token,并将它们返回给客户端。Access Token用于访问受保护的API,Refresh Token用于获取新的Access Token。
什么是无感知刷新Token
无感知刷新Token是指,在Token过期之前,系统自动使用Refresh Token获取新的Access Token,从而实现Token的无感知刷新,用户可以无缝继续使用应用。
在实现无感知刷新Token的过程中,需要考虑以下几个方面:
- 如何判断Token是否过期?
- 如何在Token过期时自动使用Refresh Token获取新的Access Token?
- 如何处理Refresh Token的安全问题?
下面将介绍如何实现无感知刷新Token的具体步骤。
实现步骤
步骤一:获取Access Token和Refresh Token
在认证成功后,需要将Access Token和Refresh Token发送给客户端。Access Token用于访问受保护的API,Refresh Token用于获取新的Access Token。可以使用JWT(JSON Web Token)或OAuth2(开放授权)等方式实现认证。
在JWT中,可以使用如下代码生成Access Token和Refresh Token:
const accessToken = jwt.sign({userId: '123'}, 'ACCESS_TOKEN_SECRET', {expiresIn: '15m'}); const refreshToken = jwt.sign({userId: '123'}, 'REFRESH_TOKEN_SECRET', {expiresIn: '7d'});
步骤二:在请求中携带Access Token
在每个需要认证的API请求中,需要在请求头中携带Access Token,如下所示:
GET /api/user HTTP/1.1 Host: example.com Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
在前端中,可以使用Axios等库设置请求头:
axios.defaults.headers.common['Authorization'] = `Bearer ${accessToken}`;
步骤三:拦截401 Unauthorized响应
在服务器返回401 Unauthorized响应时,说明Access Token已经过期,需要使用Refresh Token获取新的Access Token。可以使用Axios拦截器或Fetch API的中间件实现拦截。
在Axios中,可以使用如下代码实现拦截器:
axios.interceptors.response.use(response => { return response; }, error => { const originalRequest = error.config; if (error.response.status === 401 && !originalRequest._retry) { originalRequest._retry = true; //防止无限调用 return axios.post('/api/refresh_token', {refreshToken}) .then(response => { const { access_token, refresh_token } = response.data; localStorage.setItem('access_token', access_token); localStorage.setItem('refresh_token', refresh_token); axios.defaults.headers.common['Authorization'] = `Bearer ${access_token}`; originalRequest.headers.Authorization = `Bearer ${access_token}`; return axios(originalRequest); }); } return Promise.reject(error); });
在Fetch中,可以使用如下代码实现中间件:
function authMiddleware(request) { const access_token = localStorage.getItem('access_token'); if (access_token) { request.headers.set('Authorization', `Bearer ${access_token}`); } return request; } function tokenRefreshMiddleware(response) { if (response.status === 401) { const refreshToken = localStorage.getItem('refresh_token'); return fetch('/api/refresh_token', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ refreshToken }) }).then(response => { if (response.ok) { return response.json(); } throw new Error('Refresh Token failed'); }).then(data => { localStorage.setItem('access_token', data.access_token); localStorage.setItem('refresh_token', data.refresh_token); return Promise.resolve('refreshed'); }).catch(error => { localStorage.removeItem('access_token'); localStorage.removeItem('refresh_token'); return Promise.reject(error); }); } return Promise.resolve('ok'); } fetch('/api/user', { method: 'GET', headers: { 'Content-Type': 'application/json' }, middleware: [authMiddleware, tokenRefreshMiddleware] }).then(response => { console.log(response); }).catch(error => { console.error(error); });
在上述代码中,使用Axios或Fetch拦截器拦截401 Unauthorized响应,如果发现Access Token已经过期,则发送Refresh Token请求获取新的Access Token,并将新的Access Token设置到请求头中,重新发送请求。
步骤四:服务器处理Refresh Token请求
在服务器端,需要编写API处理Refresh Token请求,生成新的Access Token,并返回给客户端。
在JWT中,可以使用如下代码生成新的Access Token:
const accessToken = jwt.sign({userId: '123'}, 'ACCESS_TOKEN_SECRET', {expiresIn: '15m'});
在刷新Token时,需要验证Refresh Token的合法性,可以使用如下代码验证Refresh Token:
try { const payload = jwt.verify(refreshToken, 'REFRESH_TOKEN_SECRET'); const accessToken = jwt.sign({userId: payload.userId}, 'ACCESS_TOKEN_SECRET', {expiresIn: '15m'}); const refreshToken = jwt.sign({userId: payload.userId}, 'REFRESH_TOKEN_SECRET', {expiresIn: '7d'}); res.json({access_token: accessToken, refresh_token: refreshToken}); } catch (err) { res.sendStatus(401); }
在上述代码中,使用JWT的verify
方法验证Refresh Token的合法性,如果验证成功,则生成新的Access Token和Refresh Token,并返回给客户端。
步骤五:设置定时刷新Token
为了避免Access Token过期时间太长,可以设置定时刷新Token的功能。可以使用定时器或Web Workers等方式实现定时刷新Token。在每次刷新Token时,需要重新获取新的Access Token和Refresh Token,并保存到客户端。
function refreshToken() { const refreshToken = localStorage.getItem('refresh_token'); axios.post('/api/refresh_token', {refreshToken}) .then(response => { const { access_token, refresh_token } = response.data; localStorage.setItem('access_token', access_token); localStorage.setItem('refresh_token', refresh_token); axios.defaults.headers.common['Authorization'] = `Bearer ${access_token}`; }) .catch(error => { console.error(error); }); } setInterval(refreshToken, 14 * 60 * 1000); // 每14分钟刷新Token
在上述代码中,使用定时器每14分钟刷新Token。在刷新Token成功后,将新的Access Token和Refresh Token保存到客户端,并将新的Access Token设置到请求头中。
安全性考虑
在实现无感知刷新Token的过程中,需要考虑到Refresh Token的安全性问题。因为Refresh Token具有长期的有效期限,一旦Refresh Token被泄露,攻击者就可以使用Refresh Token获取新的Access Token,从而绕过认证机制,访问受保护的API。
为了增加Refresh Token的安全性,可以考虑以下几种措施:
- 将Refresh Token保存在HttpOnly Cookie中,可以避免在客户端被JavaScript获取;
- 对Refresh Token进行加密或签名,可以增加其安全性。
- 将Refresh Token保存在后端,前端通过接口和后端交互,实现刷新Access Token。
以上就是无感知刷新Token的详细内容,更多关于无感知刷新Token的资料请关注脚本之家其它相关文章!