103 lines
3.4 KiB
Markdown
103 lines
3.4 KiB
Markdown
|
|
# Auth 全局模块重写设计(跨端并存、同端互斥)
|
|||
|
|
|
|||
|
|
## 1. 目标
|
|||
|
|
|
|||
|
|
- 彻底消除 Auth 分裂状态:`token` 状态与 `AuthBloc` 状态必须单一真相源。
|
|||
|
|
- 会话策略升级为:
|
|||
|
|
- 同账号允许跨端并存:`mobile + web + desktop`
|
|||
|
|
- 同账号同端互斥:同端新登录会挤下线旧设备
|
|||
|
|
- 保证任何 401 链路在刷新失败后都能统一收敛为“未登录 + 清理本地 + 路由回到登录页”。
|
|||
|
|
- 消除设备差异导致的不一致行为(部分机型“假登录”或“卡死页”)。
|
|||
|
|
|
|||
|
|
## 2. 边界与约束
|
|||
|
|
|
|||
|
|
- 仅重写 `apps/**` 的 Auth 客户端架构与规则,不改后端协议语义。
|
|||
|
|
- 保持现有登录/注册 UI 路由入口,避免用户操作路径变化。
|
|||
|
|
- 认证属于高风险域,重写必须覆盖:
|
|||
|
|
- 启动恢复
|
|||
|
|
- 运行时 token 过期
|
|||
|
|
- 并发 401
|
|||
|
|
- 手动登出与自动过期登出的差异行为
|
|||
|
|
|
|||
|
|
## 3. 核心架构
|
|||
|
|
|
|||
|
|
### 3.1 单一真相源(Single Source of Truth)
|
|||
|
|
|
|||
|
|
- `AuthBloc` 成为唯一认证状态源。
|
|||
|
|
- `ApiInterceptor` 只负责协议级拦截与刷新,不直接做路由跳转。
|
|||
|
|
- 401 刷新失败时,`ApiInterceptor -> ApiClient callback -> AuthBloc(AuthSessionInvalidated)`。
|
|||
|
|
- 路由守卫只看 `AuthBloc` 状态,不再做隐式 token 判定。
|
|||
|
|
|
|||
|
|
### 3.2 会话状态机
|
|||
|
|
|
|||
|
|
- `AuthInitial`
|
|||
|
|
- `AuthLoading`(启动恢复/会话检查)
|
|||
|
|
- `AuthAuthenticated(user)`
|
|||
|
|
- `AuthUnauthenticated(reason)`
|
|||
|
|
|
|||
|
|
`reason` 取值:
|
|||
|
|
- `signedOut`
|
|||
|
|
- `expired`
|
|||
|
|
- `startupRecoveryFailed`
|
|||
|
|
|
|||
|
|
### 3.3 登出语义分离
|
|||
|
|
|
|||
|
|
- 手动登出:`deleteSession()`
|
|||
|
|
- 尝试调用后端注销
|
|||
|
|
- 最终清本地
|
|||
|
|
- 自动过期:`clearSessionLocalOnly()`
|
|||
|
|
- 仅清本地
|
|||
|
|
- 不调用后端注销接口
|
|||
|
|
|
|||
|
|
### 3.4 并发与抖动控制
|
|||
|
|
|
|||
|
|
- `ApiInterceptor` 继续使用 refresh singleflight。
|
|||
|
|
- 新增 auth failure singleflight:多并发 401 刷新失败,只触发一次全局会话失效事件。
|
|||
|
|
|
|||
|
|
### 3.5 设备差异治理
|
|||
|
|
|
|||
|
|
- 启动时 token 读取异常必须兜底:进入 `AuthUnauthenticated(startupRecoveryFailed)`,避免卡死在 Boot。
|
|||
|
|
- `FlutterSecureStorage` 显式平台配置:
|
|||
|
|
- Android 使用 `encryptedSharedPreferences`
|
|||
|
|
- iOS 指定 keychain accessibility(保证行为稳定)
|
|||
|
|
|
|||
|
|
## 4. 数据流
|
|||
|
|
|
|||
|
|
### 4.1 冷启动
|
|||
|
|
|
|||
|
|
1. `main` 触发 `AuthStarted`
|
|||
|
|
2. `AuthBloc` 读取 refresh token
|
|||
|
|
3. 有 refresh token -> 刷新会话 -> 成功进入 `AuthAuthenticated`
|
|||
|
|
4. 无 token 或异常 -> `AuthUnauthenticated(startupRecoveryFailed)`
|
|||
|
|
|
|||
|
|
### 4.2 运行时 API 请求
|
|||
|
|
|
|||
|
|
1. 请求携带 access token
|
|||
|
|
2. 401 -> 触发 refresh
|
|||
|
|
3. refresh 成功 -> 自动重试原请求
|
|||
|
|
4. refresh 失败 -> 触发一次全局 auth failure
|
|||
|
|
5. `AuthBloc` 收到 `AuthSessionInvalidated(expired)` -> 清本地 -> `AuthUnauthenticated(expired)`
|
|||
|
|
6. Router 根据状态回登录页
|
|||
|
|
|
|||
|
|
## 5. 测试策略
|
|||
|
|
|
|||
|
|
- `AuthBloc`:
|
|||
|
|
- 启动读取 refresh token 异常兜底
|
|||
|
|
- session invalidated 事件导致统一未登录
|
|||
|
|
- `ApiInterceptor`:
|
|||
|
|
- 并发 401 refresh 失败仅触发一次 auth failure
|
|||
|
|
- `AuthRepository`:
|
|||
|
|
- 手动登出 vs 自动过期清理行为差异
|
|||
|
|
|
|||
|
|
## 6. 迁移计划
|
|||
|
|
|
|||
|
|
- 先引入新事件/新回调/新状态原因,不改 UI 交互。
|
|||
|
|
- 再改路由守卫识别未登录原因。
|
|||
|
|
- 最后补齐 `apps/AGENTS.md` Auth 强约束,防止后续回归为“各处乱写”。
|
|||
|
|
|
|||
|
|
## 7. 风险与回滚
|
|||
|
|
|
|||
|
|
- 风险:回调链路接错导致频繁误登出。
|
|||
|
|
- 规避:并发 singleflight + 精确触发条件(仅 401 refresh 失败)。
|
|||
|
|
- 回滚:保留旧事件兼容层,出现异常可快速退回旧路由判定。
|