前言
在前后端分离的应用中,使用token进行交互验证是一种比较常见的方式。但是,由于token的有效期限制,需要不断地刷新token,否则会导致用户认证失败。
栗子
可以幻想一下,你在一个应用中,填写了很多个表单,最后提交的时候,401认证失败,这个时候你心里肯定一万个…所以为了解决这个问题,给用户更好的体验,本文介绍无感知刷新token的实现。
token验证的原理
在web应用中,常见的token认证方式有基于token和基于cookie的认证。基于token的认证方式是,将认证信息每次放在请求头中,在每次发起请求时,将token发给服务端认证,而基于cookie的认证方式是,客户端本地存储的cookie文件来记录用户的操作状态在随后的访问请求中,客户端浏览器会检索与用户请求资源相匹配的Cookie,并将其提交给Web服务器进行验证。如果Cookie合法,则允许用户访问请求的资源,反之则拒绝用户的访问请求。
什么是无感知刷新token
无感知刷新Token是指在Token过期之前,系统自动使用Refresh Token获取新的Access Token,从而实现Token的无感知刷新,用户可以无缝继续使用应用。在实现无感知刷新token的时候需要考虑以下几点:
- 如何判断token是否过期?
- 如何在token过期时,自动使用Refresh Token获取新的Access Token?
- Refresh Token安全性?
代码实现-第一步
使用axios作为客户端请求库
1import axios from 'axios';
2
3function setTokens(data) {
4 localStorage.setItem('access_token', data.access_token);
5 localStorage.setItem('refresh_token', data.refresh_token);
6}
7
8async function login() {
9 const response = await axios.post('/login', { username: 'USERNAME', password: 'PASSWORD' });
10 setTokens(response.data);
11}
代码实现-第二步
设置请求头,添加token
1axios.defaults.headers.common['Authorization'] = `Bearer ${accessToken}`;
有不明白axios基本设置的同学,可以查看axios官方文档
代码实现-第三步
拦截401 Authorization状态
1
2axios.interceptors.response.use(undefined, (error) => {
3 if (error.response && error.response.status === 401 && localStorage.getItem('refresh_token')) {
4 return axios.post('/refresh_token', { refresh_token: localStorage.getItem('refresh_token') })
5 .then((response) => {
6 setTokens(response.data);
7 error.config.headers['Authorization'] = 'Bearer ' + response.data.access_token;
8 return axios(error.config);
9 });
10 } else {
11 return Promise.reject(error);
12 }
13});
代码实现-第四步
设置定时刷新token
为了避免Access Token过期时间太长,可以使用定时器定时刷新token,在每次刷新后重新设置Access Token
和Refresh Token
1function refreshToken() {
2 const refreshToken = localStorage.getItem('refresh_token');
3 axios.post('/refresh_token', { refresh_token: localStorage.getItem('refresh_token'))
4 .then(response => {
5 setTokens(response.data);
6 axios.defaults.headers.common['Authorization'] = `Bearer ${access_token}`;
7 })
8 .catch(error => {
9 console.error(error);
10 });
11}
12
13setInterval(refreshToken, 5 * 60 * 1000);
安全性考虑
在实现无感知刷新Token的过程中,需要考虑到Refresh Token的安全性问题。因为Refresh Token具有长期的有效期限,一旦Refresh Token被泄露,攻击者就可以使用Refresh Token获取新的Access Token,从而绕过认证机制,访问受保护的API。
- 限制访问次数
- 请求加签(目前团队中大佬已经实现加签)
- 加密
后续优化
在实现过程中,会发现该实现方式大部分都是在请求拦截和相应拦截中设置的,这样带来的问题就是,耦合性高,接口重试的机制不太好。另一方面,我觉得token无感知刷新
涉及到了接口重发,我理解的是接口维度的。所以在后续会考虑封装一个class类来实现。