5.6 KiB
5.6 KiB
/api/auth/refresh 轮换落地设计
日期:2026-04-21
1. 文档目的
这份文档用于指导 M2 中 实现 refresh token 轮换 任务的首版落地,冻结:
POST /api/auth/refresh的请求与响应 contract。- refresh cookie、服务端 refresh session 与 access token 三者的职责边界。
- “会话 ID 稳定、refresh token 可轮换”的固定语义。
- Rust 首版在未切入 SpacetimeDB 前的临时进程内实现方式。
2. 当前基线
当前 Node /api/auth/refresh 已具备以下稳定语义:
- 从 HttpOnly cookie 中读取原始 refresh token。
- 服务端只按
refresh_token_hash查找当前活跃会话。 - refresh 成功后,不新建第二条会话,而是在原会话上轮换 refresh token。
- 轮换时会更新
expires_at与last_seen_at。 - 成功后返回新的 access token,并写回新的 refresh cookie。
- 失败时会主动清空 refresh cookie,要求前端重新登录。
Rust 首版必须保留以上语义。
3. 当前阶段范围
本阶段只落以下内容:
module-auth增加进程内 refresh session 真相与轮换服务。api-server暴露POST /api/auth/refresh。- 登录成功时创建 refresh session。
- refresh 成功时在原 session 上轮换 refresh token。
- access token 的
sid固定改为稳定session_id,不再直接复用 refresh token。
本阶段不包含:
/api/auth/logout/api/auth/logout-all/api/auth/sessions/api/auth/sessions/:sessionId/revoke- SpacetimeDB reducer 真正写表
4. contract
4.1 请求
- 方法:
POST - 路径:
/api/auth/refresh - 请求体:空
- 鉴权来源:refresh cookie
4.2 成功响应
{
"token": "<access-token>"
}
同时响应头必须写回新的 refresh cookie。
4.3 失败响应
当 refresh token 缺失、会话不存在、会话已过期或用户不存在时:
- 返回
401 UNAUTHORIZED - 同时清理 refresh cookie
5. 固定语义
5.1 session_id 与 refresh token 必须拆开
从本任务开始固定以下规则:
session_id是稳定会话主键。- refresh token 是可轮换的会话凭证。
- access token 的
sid必须写入session_id。 - refresh 轮换只更新 refresh token,不更改
session_id。
禁止继续把 refresh token 直接塞进 JWT sid。
5.2 refresh 是“原会话轮换”
refresh 成功后:
- 保留原
session_id - 生成新的原始 refresh token
- 用新的
refresh_token_hash覆盖旧值 - 更新
expires_at - 更新
last_seen_at
不允许新建第二条 session。
6. crate 边界
6.1 module-auth
负责:
- 管理 refresh session 进程内真相。
- 提供创建 refresh session 与轮换 refresh session 的用例。
- 提供按
user_id查询用户快照的能力,供 refresh 成功后重新签发 access token。
不负责:
- 生成原始 refresh token。
- 读写 cookie。
- 签发 JWT。
6.2 platform-auth
负责:
- 生成原始 refresh token。
- 对 refresh token 做哈希。
- 构造 refresh cookie 的
Set-Cookie头。 - 从 cookie header 中读取 refresh token。
6.3 api-server
负责:
- 从请求 cookie 中提取 refresh token。
- 调用
module-auth执行 refresh session 轮换。 - 根据用户快照与稳定
session_id重新签发 access token。 - refresh 失败时清理 cookie。
7. 进程内存储模型
当前阶段 module-auth 继续使用进程内内存仓储承接 refresh session,字段至少包括:
session_iduser_idrefresh_token_hashissued_by_providerexpires_atcreated_atupdated_atlast_seen_atrevoked_at
说明:
- 这只是 SpacetimeDB 正式落地前的阶段性实现。
- 字段命名与语义继续对齐 SPACETIMEDB_REFRESH_SESSION_TABLE_DESIGN_2026-04-21.md。
8. 流程
8.1 登录创建 session
密码登录成功后:
api-server生成原始 refresh token。api-server计算refresh_token_hash。module-auth创建一条新 session,并返回稳定session_id。api-server用该session_id写入 access token 的sid。api-server把原始 refresh token 写回 cookie。
8.2 refresh 轮换 session
当请求 POST /api/auth/refresh 时:
- 从 cookie 中读取原始 refresh token。
- 计算
refresh_token_hash。 module-auth查找当前活跃 session。- 校验
expires_at > now且revoked_at == null。 - 读取该 session 对应用户。
- 生成新的原始 refresh token。
- 用新 hash 更新同一条 session。
- 返回新的 access token 与新的 refresh cookie。
9. 错误语义
以下情况统一返回 401:
- 缺少 refresh cookie
- refresh token 命中不到 session
- refresh session 已过期
- refresh session 已吊销
- session 对应用户不存在
错误文案统一保持中文,并沿用“当前登录态已失效,请重新登录”这类恢复导向语义。
10. 测试策略
至少覆盖:
- 登录成功后可用 cookie 调用
/api/auth/refresh - refresh 成功会写回新的 cookie
- refresh 成功返回新的 access token
- refresh 后旧 refresh token 立即失效
- 缺少 cookie 时返回
401 - 无效 refresh token 时返回
401且清理 cookie
11. 完成定义
满足以下条件时,本任务视为完成:
- Rust 侧已提供
POST /api/auth/refresh。 - access token
sid已改为稳定session_id。 - refresh token 轮换成功时不创建新会话。
- refresh 失败时会清理 cookie。
- 文档、任务清单与测试已同步更新。