无感刷新 Token:如何做到让用户“永不掉线”

出于安全考虑,用于身份验证的 Token​(通常是 Access Token)必须有较短的有效期。那么,我们如何在保证安全的前提下,创造一种“永不掉线”的丝滑体验呢?

没有什么比在用户操作得正嗨时,突然提示“登录已过期,请重新登录”的提示更让人沮丧的了。这种突兀的中断不仅破坏了用户体验,甚至可能导致未保存的数据丢失。

然而,我们都知道,出于安全考虑,用于身份验证的 Token(通常是 Access Token)必须有较短的有效期。那么,我们如何在保证安全的前提下,创造一种“永不掉线”的丝滑体验呢?

问题的根源:Access Token 的“天生矛盾”

首先,我们要理解为什么需要刷新 Token。

我们通常使用 Access Token 来验证用户的每一次 API 请求。为了安全,Access Token 的生命周期被设计得很短(例如 30 分钟或 1 小时)。如果有效期太长,一旦泄露,攻击者就能在很长一段时间内冒充用户进行操作,风险极高。

这就产生了一个矛盾:

  • 安全性要求:Access Token 有效期要短。
  • 用户体验要求:用户不想频繁地被强制重新登录。

为了解决这个矛盾,Refresh Token 应运而生。

核心理念:双 Token 认证系统

无感刷新机制的核心在于引入了两种类型的 Token:

(1) Access Token(访问令牌)

  • 用途:用于访问受保护的 API 资源,附加在每个请求的 Header 中。
  • 特点:生命周期短(如 1 小时),无状态,服务器无需存储。
  • 存储:通常存储在客户端内存中(如 Vuex/Redux),因为需要频繁读取。

(2) Refresh Token(刷新令牌)

  • 用途:当 Access Token 过期时,专门用于获取一个新的 Access Token。
  • 特点:生命周期长(如 7 天或 30 天),与特定用户绑定,服务器需要安全存储其有效性记录。
  • 存储:必须安全存储。最佳实践是存储在 HttpOnly Cookie 中,这样可以防止客户端 JavaScript 脚本(如 XSS 攻击)读取它。

既然如此,为何不直接使用 Refresh Token 呢?

Access Token 通常是无状态的,服务器无需记录它,也导致 JWT 无法主动吊销,而 Refresh Token 是有状态的,服务器需要一个列表(数据库中的“白名单”或“吊销列表”)来记录哪些 Refresh Token 是有效的,当用户更改密码、或从某个设备上“主动登出”时,服务器端可以主动将对应的 Refresh Token 设为无效。

无感刷新的详细工作流

下面是这个“魔法”发生的具体步骤:

(1) 首次登录:用户使用用户名和密码登录。服务器验证成功后,返回一个 Access Token 和一个 Refresh Token。

(2) 正常请求:客户端将 Access Token 存储起来,并在后续的每次 API 请求中,通过 Authorization 请求头将其发送给服务器。

(3) Token 过期:当 Access Token 过期后,客户端再次用它请求 API。服务器会拒绝该请求,并返回一个特定的状态码,通常是 401 Unauthorized。

(4) 拦截 401 错误:客户端的请求层(如 Axios 拦截器)会捕获这个 401 错误。此时,它不会立即通知用户“你已掉线”,而是暂停这个失败的请求。

(5) 发起刷新请求:拦截器使用 Refresh Token 去调用一个专门的刷新接口(例如 /api/auth/refresh)。

(6) 处理刷新结果:

  • 刷新成功:服务器验证 Refresh Token 有效,生成一个新的 Access Token(有时也会返回一个新的 Refresh Token,这被称为“刷新令牌旋转”策略,可以提高安全性),并将其返回给客户端。
  • 刷新失败:如果 Refresh Token 也过期了或无效,服务器会返回错误(如 403 Forbidden)。这意味着用户的登录会话彻底结束。

(7) 重试与终结:

  • 若刷新成功:客户端用新的 Access Token 自动重发刚才失败的那个 API 请求。用户完全感觉不到任何中断,数据操作无缝衔接。
  • 若刷新失败:客户端清除所有认证信息,强制用户登出,并重定向到登录页面。
实战演练:使用 Axios 拦截器实现无感刷新

Axios 的拦截器是实现这一流程的完美工具。下面是一个完整且考虑了并发问题的实现方案。

(1) 创建 Axios 实例

首先,我们创建一个单独的 Axios 实例,方便统一管理。

// a-pi/request.js
import axios from 'axios';

const service = axios.create({
 baseURL: '/api',
 timeout: 10000,
});

// 请求拦截器
service.interceptors.request.use(
 config => {
    // 在发送请求之前,从 state management (e.g., Vuex/Pinia/Redux) 获取 token
    const accessToken = getAccessTokenFromStore(); 
    if (accessToken) {
      config.headers['Authorization'] = `Bearer ${accessToken}`;
    }
    return config;
  },
 error => {
    return Promise.reject(error);
  }
);

(2) 核心:响应拦截器

这是实现无感刷新的关键。

// a-pi/request.js (续)

// 用于刷新 token 的 API
import { refreshTokenApi } from './auth'; 

let isRefreshing = false; // 控制刷新状态的标志
let requests = []; // 存储因 token 过期而挂起的请求

service.interceptors.response.use(
 response => response, // 对成功响应直接返回
 async error => {
    const { config, response: { status } } = error;
    
    // 1. 如果不是 401 错误,直接返回错误
    if (status !== 401) {
      return Promise.reject(error);
    }

    // 2. 避免重复刷新:如果正在刷新 token,将后续请求暂存
    if (isRefreshing) {
      return new Promise(resolve => {
        requests.push(() => resolve(service(config)));
      });
    }

    isRefreshing = true;

    try {
      // 3. 调用刷新 token 的 API
      const { newAccessToken } = await refreshTokenApi(); // 假设 refresh token 通过 HttpOnly cookie 自动发送

      // 4. 更新本地存储的 access token
      setAccessTokenInStore(newAccessToken);
      
      // 5. 重试刚才失败的请求
      config.headers['Authorization'] = `Bearer ${newAccessToken}`;
      
      // 6. 重新执行所有被挂起的请求
      requests.forEach(cb => cb());
      requests = []; // 清空队列
      
      return service(config); // 返回重试请求的结果
    } catch (refreshError) {
      // 7. 如果刷新 token 也失败了,则执行登出操作
      console.error('Unable to refresh token.', refreshError);
      logoutUser(); // 清除 token,重定向到登录页
      return Promise.reject(refreshError);
    } finally {
      isRefreshing = false;
    }
  }
);

export default service;

代码解析:

并发处理:isRefreshing 标志和 requests 数组是关键。当第一个 401 错误触发刷新时,isRefreshing 变为 true。后续在刷新完成前到达的 401 请求,都会被推进 requests 队列中挂起,而不是重复发起刷新请求。当刷新成功后,再遍历队列,依次执行这些被挂起的请求。

原子操作:通过这种“加锁”机制,确保了刷新 Token 的操作是原子的,避免了资源浪费和潜在的竞态条件。

  • 优雅降级:当 Refresh Token 也失效时,系统会执行 logoutUser(),进行清理工作并引导用户重新登录,这是一个优雅的失败处理方案。

无感刷新 Token 机制是现代 Web 应用提升用户体验的“标配”。它将身份验证的复杂性隐藏在后台,为用户提供了一个流畅、不间断的操作环境。

实现这一机制,不仅仅是写几行代码,更是对认证流程、安全性和用户体验三者之间平衡的深刻理解。

AI大模型学习福利

作为一名热心肠的互联网老兵,我决定把宝贵的AI知识分享给大家。 至于能学习到多少就看你的学习毅力和能力了 。我已将重要的AI大模型资料包括AI大模型入门学习思维导图、精品AI大模型学习书籍手册、视频教程、实战学习等录播视频免费分享出来。

一、全套AGI大模型学习路线

AI大模型时代的学习之旅:从基础到前沿,掌握人工智能的核心技能!

因篇幅有限,仅展示部分资料,需要点击文章最下方名片即可前往获取

二、640套AI大模型报告合集

这套包含640份报告的合集,涵盖了AI大模型的理论研究、技术实现、行业应用等多个方面。无论您是科研人员、工程师,还是对AI大模型感兴趣的爱好者,这套报告合集都将为您提供宝贵的信息和启示。

因篇幅有限,仅展示部分资料,需要点击文章最下方名片即可前往获

三、AI大模型经典PDF籍

随着人工智能技术的飞速发展,AI大模型已经成为了当今科技领域的一大热点。这些大型预训练模型,如GPT-3、BERT、XLNet等,以其强大的语言理解和生成能力,正在改变我们对人工智能的认识。 那以下这些PDF籍就是非常不错的学习资源。


因篇幅有限,仅展示部分资料,需要点击文章最下方名片即可前往获

四、AI大模型商业化落地方案

因篇幅有限,仅展示部分资料,需要点击文章最下方名片即可前往获

作为普通人,入局大模型时代需要持续学习和实践,不断提高自己的技能和认知水平,同时也需要有责任感和伦理意识,为人工智能的健康发展贡献力量

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值