# Auth refresh session 持久化热修方案 日期:`2026-04-24` ## 1. 背景 当前 Rust 鉴权链路已经具备 refresh cookie 自动续签能力:access token 过期后,前端会调用 `POST /api/auth/refresh`,后端轮换 refresh token 并返回新的 access token。 但 `module-auth` 当前仍使用进程内 `InMemoryAuthStore` 保存账号与 refresh session。只要 `server-rs` 在 access token 生命周期内发生重启,浏览器侧 HttpOnly cookie 仍然存在,服务端却找不到对应账号与 session,最终表现为约 `JWT_EXPIRES_IN` 后需要重新登录。 ## 2. 本次目标 本次先落一个低风险持久化闭环,解决“后端重启导致 2 小时后必须重新登录”的线上体验问题: 1. 为当前 `InMemoryAuthStore` 增加 UTF-8 JSON 快照文件。 2. 在账号、手机号索引、微信身份、refresh session 发生变更后自动保存快照。 3. `api-server` 启动时从配置路径恢复快照。 4. 保持现有 `/api/auth/refresh`、`logout`、`sessions` 语义不变。 ## 3. 非目标 本次不把完整认证域一次性迁入 SpacetimeDB 表,原因是 refresh session 独立持久化不足以解决问题:refresh 成功后还需要按 `user_id` 读取账号快照重新签发 access token,因此账号主数据也必须同源恢复。 SpacetimeDB 正式表接管仍按以下既有文档继续推进: 1. `docs/technical/SPACETIMEDB_AUTH_USER_ACCOUNT_TABLE_DESIGN_2026-04-21.md` 2. `docs/technical/SPACETIMEDB_AUTH_IDENTITY_TABLE_DESIGN_2026-04-21.md` 3. `docs/technical/SPACETIMEDB_REFRESH_SESSION_TABLE_DESIGN_2026-04-21.md` ## 4. 配置 新增环境变量: | 变量 | 默认值 | 说明 | | --- | --- | --- | | `GENARRATIVE_AUTH_STORE_PATH` | `server-rs/.data/auth-store.json` | 当前 Rust 鉴权快照文件路径。相对路径按进程工作目录解析。 | ## 5. 数据边界 快照文件保存当前 Rust 鉴权服务已经在内存中维护的最小真相: 1. `next_user_id` 2. `users_by_username` 3. `phone_to_user_id` 4. `sessions_by_id` 5. `session_id_by_refresh_token_hash` 6. `wechat_identity_by_provider_uid` 7. `user_id_by_provider_union_id` 短信验证码和微信 OAuth state 不持久化,原因是它们是短生命周期挑战数据,重启后失效是可接受行为。 ## 6. 安全约束 1. refresh token 原文仍只存在浏览器 HttpOnly cookie,快照只保存 `sha256(refresh_token)`。 2. 快照包含 `password_hash`、手机号映射和 refresh token hash,部署时必须放在服务端私有目录,不允许暴露到静态资源目录。 3. 快照写入必须使用 UTF-8,并通过临时文件原子替换降低写坏风险。 ## 7. 后续 SpacetimeDB 接管点 当 `user_account`、`auth_identity`、`refresh_session` 表及 reducer 全部落地后,替换策略如下: 1. 保留 `module-auth` 用例语义。 2. 把当前快照仓储替换为 SpacetimeDB 仓储适配器。 3. 启动时可提供一次性导入脚本,把 JSON 快照导入 SpacetimeDB 表。 4. 导入完成后禁用 `GENARRATIVE_AUTH_STORE_PATH` 快照写入。