当前位置: 首页 > news >正文

5分钟搞定前后端无感刷新:accessToken与refreshToken实战指南(含axios拦截器配置)

5分钟搞定前后端无感刷新:accessToken与refreshToken实战指南(含axios拦截器配置)

在当今的Web应用开发中,用户认证是一个绕不开的话题。传统的单token方案虽然简单,但当token过期时强制用户重新登录的体验实在称不上优雅。想象一下,用户正在填写一个复杂的表单,突然因为token过期而被踢出系统,所有输入的内容都丢失了——这种体验足以让用户对产品产生负面印象。

无感刷新机制正是为了解决这一问题而生。通过accessToken和refreshToken的巧妙配合,我们可以在用户毫无感知的情况下完成token的更新,保持会话的连续性。本文将聚焦于中小型项目的快速实现方案,提供一套即插即用的解决方案,让你在5分钟内就能为项目添加这一重要功能。

1. 理解双token机制的核心原理

在开始编码之前,我们需要先理解accessToken和refreshToken这对搭档是如何协同工作的。accessToken就像是一张短期通行证,有效期通常较短(如15分钟到2小时),用于日常的API请求认证。而refreshToken则是一张长期通行证,有效期可达数天甚至数周,专门用于在accessToken过期时获取新的accessToken。

这种设计有几个关键优势:

  • 安全性:即使accessToken被截获,攻击者也只能在短时间内滥用
  • 用户体验:避免了频繁要求用户重新登录的困扰
  • 灵活性:可以随时调整refreshToken的有效期或撤销特定设备的refreshToken

提示:虽然refreshToken有效期较长,但并不意味着可以无限期使用。合理的做法是设置一个适中的有效期(如7天),并在用户主动退出时立即撤销所有refreshToken。

2. 前端实现:axios拦截器配置

axios的拦截器功能为我们实现无感刷新提供了完美的工具。下面是一个完整的实现方案:

// 创建axios实例 const service = axios.create({ baseURL: process.env.VUE_APP_BASE_API, timeout: 5000 }) // 请求拦截器 service.interceptors.request.use( config => { const accessToken = localStorage.getItem('accessToken') if (accessToken) { config.headers['Authorization'] = `Bearer ${accessToken}` } return config }, error => { return Promise.reject(error) } ) // 响应拦截器 service.interceptors.response.use( response => { return response.data }, async error => { const originalRequest = error.config // 检查是否是token过期错误 if (error.response.status === 401 && !originalRequest._retry) { originalRequest._retry = true try { // 尝试刷新token const refreshToken = localStorage.getItem('refreshToken') const res = await axios.post('/auth/refresh', { refreshToken }) // 存储新的accessToken localStorage.setItem('accessToken', res.data.accessToken) // 重试原始请求 originalRequest.headers['Authorization'] = `Bearer ${res.data.accessToken}` return service(originalRequest) } catch (refreshError) { // 刷新失败,跳转到登录页 localStorage.removeItem('accessToken') localStorage.removeItem('refreshToken') router.push('/login') return Promise.reject(refreshError) } } return Promise.reject(error) } )

这段代码实现了以下功能:

  1. 在请求头中自动添加accessToken
  2. 检测401错误(token过期)
  3. 自动使用refreshToken获取新的accessToken
  4. 重试原始请求
  5. 处理刷新失败的情况

3. 后端实现:简洁高效的token管理

后端需要提供两个关键接口:登录接口和刷新接口。以下是使用Node.js和Express的实现示例:

const jwt = require('jsonwebtoken') const redis = require('redis') // 配置 const config = { accessTokenSecret: 'your_access_secret', refreshTokenSecret: 'your_refresh_secret', accessTokenExpiry: '15m', refreshTokenExpiry: '7d' } // Redis客户端 const redisClient = redis.createClient() redisClient.on('error', (err) => console.log('Redis Client Error', err)) // 登录接口 app.post('/login', (req, res) => { const { username, password } = req.body // 验证用户凭据(简化示例) if (username !== 'admin' || password !== 'password') { return res.status(401).json({ error: 'Invalid credentials' }) } // 生成token const accessToken = jwt.sign({ username }, config.accessTokenSecret, { expiresIn: config.accessTokenExpiry }) const refreshToken = jwt.sign({ username }, config.refreshTokenSecret, { expiresIn: config.refreshTokenExpiry }) // 存储refreshToken到Redis redisClient.set(refreshToken, username) redisClient.expire(refreshToken, 60 * 60 * 24 * 7) // 7天 res.json({ accessToken, refreshToken }) }) // 刷新接口 app.post('/auth/refresh', (req, res) => { const { refreshToken } = req.body // 验证refreshToken jwt.verify(refreshToken, config.refreshTokenSecret, (err, decoded) => { if (err) { return res.status(401).json({ error: 'Invalid refresh token' }) } // 检查Redis中是否存在该refreshToken redisClient.get(refreshToken, (err, reply) => { if (!reply) { return res.status(401).json({ error: 'Refresh token expired' }) } // 生成新的accessToken const newAccessToken = jwt.sign( { username: decoded.username }, config.accessTokenSecret, { expiresIn: config.accessTokenExpiry } ) res.json({ accessToken: newAccessToken }) }) }) })

这个实现包含了几个关键点:

  • 使用不同的密钥和有效期生成accessToken和refreshToken
  • 将refreshToken存储在Redis中以便验证和管理
  • 提供清晰的错误响应

4. 常见问题与解决方案

在实际应用中,你可能会遇到以下问题:

4.1 并发请求导致的多次刷新

当多个请求同时收到401错误时,可能会触发多次刷新请求。解决方案是:

let isRefreshing = false let failedQueue = [] const processQueue = (error, token = null) => { failedQueue.forEach(prom => { if (error) { prom.reject(error) } else { prom.resolve(token) } }) failedQueue = [] } service.interceptors.response.use( response => response, async error => { const originalRequest = error.config if (error.response.status === 401 && !originalRequest._retry) { if (isRefreshing) { return new Promise((resolve, reject) => { failedQueue.push({ resolve, reject }) }).then(token => { originalRequest.headers['Authorization'] = 'Bearer ' + token return service(originalRequest) }).catch(err => { return Promise.reject(err) }) } originalRequest._retry = true isRefreshing = true try { const refreshToken = localStorage.getItem('refreshToken') const res = await axios.post('/auth/refresh', { refreshToken }) localStorage.setItem('accessToken', res.data.accessToken) processQueue(null, res.data.accessToken) originalRequest.headers['Authorization'] = 'Bearer ' + res.data.accessToken return service(originalRequest) } catch (error) { processQueue(error, null) localStorage.removeItem('accessToken') localStorage.removeItem('refreshToken') router.push('/login') return Promise.reject(error) } finally { isRefreshing = false } } return Promise.reject(error) } )

4.2 安全性最佳实践

为了确保token机制的安全性,建议遵循以下原则:

安全措施实施方法作用
HTTPS全站启用HTTPS防止token在传输中被窃取
HttpOnly Cookie将refreshToken存储在HttpOnly Cookie中防止XSS攻击窃取token
短期有效期accessToken设置较短有效期(15-120分钟)减少token泄露后的风险窗口
密钥轮换定期更换签名密钥降低密钥泄露的影响
黑名单维护已撤销但未过期的token列表及时阻止被撤销的token

4.3 性能优化

对于高并发应用,可以考虑以下优化:

  • 批量验证:当需要验证多个token时,使用Redis的批量操作命令
  • 本地缓存:在内存中缓存有效的token,减少Redis查询
  • 连接池:使用Redis连接池提高连接复用率

5. 测试与调试技巧

确保你的无感刷新机制正常工作至关重要。以下是一些测试建议:

  1. 手动测试流程

    • 登录获取token
    • 等待accessToken过期(或手动修改使其过期)
    • 发起API请求,观察是否自动刷新
    • 检查新token是否被正确存储和使用
  2. 自动化测试脚本

describe('Token Refresh', () => { let accessToken, refreshToken before(async () => { // 登录获取初始token const res = await request(app) .post('/login') .send({ username: 'test', password: 'test' }) accessToken = res.body.accessToken refreshToken = res.body.refreshToken }) it('should refresh token when expired', async () => { // 模拟token过期 const expiredToken = jwt.sign( { username: 'test' }, 'your_access_secret', { expiresIn: '-1s' } ) // 设置过期的token localStorage.setItem('accessToken', expiredToken) // 发起请求 const res = await service.get('/protected') // 验证 expect(res.status).toBe(200) expect(localStorage.getItem('accessToken')).not.toBe(expiredToken) }) })
  1. 调试工具
    • 使用Chrome开发者工具查看网络请求
    • 在拦截器中添加console.log调试关键步骤
    • 使用Redis CLI监控token存储情况

在实际项目中,我发现设置合理的token过期时间非常重要。accessToken太短会导致频繁刷新,太长则安全性降低。经过多次测试,15-30分钟的accessToken配合7天的refreshToken在大多数场景下都能取得良好的平衡。

http://www.jsqmd.com/news/518635/

相关文章:

  • LLM之Agent(四十)|AI Agents(九):从单体到多体——构建可协作的智能体网络
  • 探索大数据领域Kafka的消息传输奥秘
  • C#ADO编程与事务思维导图
  • 保姆级避坑指南:在Ubuntu 22.04上对NVMe SSD执行PCIe FLR功能级复位
  • 创建对象
  • 新手必看!Qt中误用close()导致的3大内存问题(附正确姿势)
  • FLAC3D模拟下的不规则形状切片云图解析与应用研究
  • 用Python+OpenCV实现多视角3D重建:从照片到模型的完整流程
  • 揭秘!AI应用架构师如何搭建高效AI伦理治理框架,实现负责任AI
  • Ubuntu上安装、使用Redis的详细教程
  • 电动汽车再生制动系统Simulink联合Carsim仿真模型:模拟不同工况下的车辆参数
  • STM32F030 永磁同步电机非线性磁链观测器的奇妙之旅
  • COMSOL多槽结构石墨烯宽谱吸收仿真分析
  • 四旋翼无人机Simulink轨迹跟踪:应用MPC的稳定控制研究
  • 高效团队协作实践:基于Wiki.js与cpolar的跨地域知识管理方案
  • Visual Studio 2022实战:5分钟搞定.NET MAUI跨平台应用开发(附常见问题解决)
  • 5分钟搞定:用天地图API v4.0 + GeoJSON快速绘制中国行政区划地图(附完整源码)
  • CSS常用动态样式详解:让网页“活”起来的秘密武器
  • Matlab电力系统仿真实例:单相接地、两相间短路和三相短路故障波形模拟
  • 从网格划分到结果后处理:手把手带你用Fluent完成一次完整的LES大涡模拟(含SGS模型设置避坑)
  • PubChemPy避坑指南:解决化合物数据获取中的5个常见错误
  • BigDecimal转字符串踩坑实录:为什么你的123.00变成了1.23E+2?
  • HPE磁盘阵列管理04——MSA事件诊断与实战处理指南
  • 双向全桥CLLC拓扑变频控制仿真模型:实现软开关与谐振状态观察,默认2018b版本分析
  • MPC模型预测控制在Matlab Simulink联合仿真中的探索
  • 逆向工程实战:手把手教你破解药监局网站的动态数据加载机制(Python+Chrome开发者工具)
  • Cesium地图开发实战:如何用原生Canvas打造可交互的指北针组件
  • 解锁LyricsX高效配置:让你的macOS歌词体验无缝升级
  • 实战Pikachu靶场:SSRF漏洞利用与防御全攻略(附常见函数解析)
  • Codesys变量类型全解析:从基础到实战避坑指南