Skip to main content

你将构建什么

一个生产级的 Token 管理层——覆盖持久化存储选型、多用户隔离、并发刷新竞争、过期降级、主动撤销和安全存储,确保你的 SaaS 应用在 Token 轮换和服务重启后仍然正常工作。

Token 生命周期

核心原则

违反以下任何一条,都会导致用户静默掉线——没有错误提示,只有突然的「请重新登录」。
1. 刷新后必须持久化report_eventon_refresh 回调(或手动调用 refresh() 后)是你保存新 Token 的唯一时机。错过它,旧 Refresh Token 已失效,新 Token 丢失,用户必须重新授权。 2. 禁止纯内存存储 — 服务重启 = 所有用户 Token 丢失 = 全体重新登录。Token 必须落盘(DB / Redis / 文件)。 3. 轮换必须原子化 — 每次 refresh() 调用后,旧的 Access Token 和 Refresh Token 同时失效。保存新 Token 和废弃旧 Token 必须在同一事务中完成。

Step 1: 选择存储策略

ProfyApp 不内置 Token 存储——它只负责 OAuth 握手和 API 调用。存储是你的应用的职责,这保证了最大灵活性。

初始化 ProfyApp + 存储

ProfyApp 只持有 App 身份,不存储用户 Token。Token 在每次 API 调用时传入,刷新后通过 on_refresh 回调持久化。

Step 2: 多用户 Token 管理

SaaS 应用通常为每个用户维护独立的 Token。核心模式:以 userId 为键,隔离读写
revoke() 接收的是 refresh token(不是 access token)。access token 是无状态 JWT,无法撤销;revoke 使 refresh token 失效从而阻止后续刷新。

Step 3: 并发刷新竞争

问题:两个请求同时发现 Access Token 过期,都尝试刷新。第一个成功,第二个因 Refresh Token 已轮换而收到 invalid_grant 解决方案:用互斥锁保证同一用户同一时刻只有一个刷新操作。
Python 版本在获取锁后重新检查 Token 是否已被其他协程刷新(double-check pattern)。TypeScript 版本通过共享同一个 Promise 实现相同效果。
对于多实例部署(多 Pod / 多进程),进程内的 Map / dict 锁不够。需要分布式锁:

Step 4: Token 过期降级

当 Refresh Token 也过期(90 天未使用)或被撤销时,refresh() 抛出 AuthenticationError(401)。此时唯一的恢复路径是引导用户重新授权。
前端处理 auth_expired 响应:
前端降级组件

Step 5: Token 撤销

用户主动断开连接(取消授权)或你需要清理不再使用的 Token 时,调用撤销接口。
revoke() 接收 refresh token。撤销后,该用户的 Access Token(1 小时内自然过期)和 Refresh Token 立即失效。后续 API 调用会收到 401。确保在撤销前已完成所有进行中的请求。
撤销的典型触发场景:
  • 用户在你的 App 设置页点击「断开 Profy 连接」
  • 用户删除账号
  • 管理员批量清理不活跃授权
  • 检测到异常访问模式

Step 6: 安全存储

静态加密

Token 等同于用户凭证,在数据库中应加密存储:

安全清单

反模式

以下是生产环境中常见的 Token 管理错误。

1. 前端 localStorage 存储 Token

Token 应该只存在于服务端。前端通过 HttpOnly Cookie 或 Session ID 引用。

2. 忽略 on_refresh / 手动刷新后不持久化

刷新后旧 Refresh Token 已失效。如果新 Token 没有保存,下次刷新会失败,用户被迫重新授权。

3. 使用已轮换的 Refresh Token

每次从持久化存储读取最新 Token,不要依赖内存缓存。

4. 不处理并发刷新

参照 Step 3 的互斥锁模式。

生产环境清单

上线前逐项确认:
  • Token 刷新后立即写入持久化存储(on_refresh 回调 或 手动 refresh() 后 save)
  • Token 存储于服务端(DB / Redis),不在前端
  • Token 在存储层加密
  • 并发刷新有互斥锁保护
  • AuthenticationError 有降级处理(引导重新授权)
  • 用户断开连接时调用 revoke(refreshToken) 清理
  • 日志不包含完整 Token 值
  • OAuth scope 遵循最小权限(events:write
  • 多实例部署使用分布式锁
  • 加密密钥不硬编码在代码中

下一步

按次计费实战

使用 ProfyApp report_event 实现按次扣费

Next.js 全栈集成

从零构建完整的 OAuth + 计费 Next.js 应用

Python FastAPI 集成

Python SDK + Token 持久化

SDK 完整指南

双语言 API 详解