# 多端登录会话身份模型设计 日期:`2026-04-21` ## 1. 文档目的 这份文档用于补齐当前鉴权体系中“多端登录识别”能力的落地设计,目标是让后端能够稳定标识: 1. 不同设备上的浏览器登录 2. 同一设备上的不同浏览器登录 3. 微信小程序、支付宝小程序、抖音小程序等小程序来源 4. 微信内 H5 与普通浏览器 H5 的差异 同时冻结: 1. `refresh_session` 需要新增的客户端身份字段 2. 前端与网关需要补传的上下文 3. Axum 派生展示名与“当前设备”标识的规则 4. 与后续 `/api/auth/sessions`、`/api/auth/sessions/:sessionId/revoke` 的契约关系 ## 2. 当前问题 当前项目里“设备类型”只有最小占位语义: 1. Node `buildAuthRequestContext(...)` 直接写死 `clientType = "browser"` 2. `clientLabel` 只是按 `User-Agent` 粗略显示“移动端浏览器 / 网页端浏览器” 3. `refresh_session` / `user_sessions` 里没有区分运行时、小程序来源、浏览器实例与设备实例的结构化字段 这会导致: 1. 无法区分“同一台电脑上的 Chrome 和 Edge” 2. 无法稳定标记“微信小程序登录”和“微信内 H5 登录” 3. 无法给会话列表生成稳定、可读的端侧标签 ## 3. 设计目标 新的会话客户端身份模型必须满足: 1. 会话列表能区分“设备”与“客户端运行时” 2. 同设备不同浏览器被视为不同登录端 3. 小程序来源可稳定区分到平台级别 4. 前端不需要拿到真实设备名称,也能展示稳定友好标签 5. 浏览器拿不到真实设备名时,系统仍能自动生成展示名 ## 4. 基本原则 ### 4.1 不追求真实设备名 浏览器端通常无法拿到: 1. 系统设置里的设备名称 2. 手机自定义名称 3. 电脑主机名 因此本设计不依赖真实设备名称,而是生成“会话展示名”。 ### 4.2 会话识别拆三层 当前阶段统一拆为: 1. `client_type` 大类,例如浏览器、小程序、桌面客户端 2. `client_runtime` 具体运行时,例如 Chrome、Safari、微信小程序 3. `client_instance_id` 客户端实例 ID,用于区分“同设备不同浏览器”以及“同浏览器不同安装/清缓存” ### 4.3 展示名由后端派生 `device_display_name` 不直接信任前端自由文本,而由后端基于结构化字段派生,必要时允许前端提供候选值。 ## 5. 字段模型 `refresh_session` 建议新增以下字段: | 字段名 | 类型 | 必填 | 说明 | | --- | --- | --- | --- | | `client_type` | `String` | 是 | 大类,见 6.1 | | `client_runtime` | `String` | 是 | 运行时,见 6.2 | | `client_platform` | `String` | 否 | 平台,如 `windows`、`macos`、`ios`、`android` | | `client_instance_id` | `String` | 否 | 客户端实例 ID,由前端持久化生成 | | `device_fingerprint` | `String` | 否 | 后端派生指纹,用于聚合同设备近似会话 | | `device_display_name` | `String` | 是 | 给用户展示的设备/端侧名称 | | `mini_program_app_id` | `Option` | 否 | 小程序 appid | | `mini_program_env` | `Option` | 否 | 小程序环境,如 `develop`、`trial`、`release` | | `user_agent` | `Option` | 否 | 原始 UA | | `ip` | `Option` | 否 | 登录 IP | 说明: 1. `client_type` 和 `client_runtime` 是强约束字段,必须稳定可枚举。 2. `client_instance_id` 用于区分“同一台设备上的不同浏览器/不同安装实例”。 3. `device_fingerprint` 不是鉴权凭据,只用于会话展示聚类。 4. `device_display_name` 是最终展示字段。 ## 6. 枚举定义 ### 6.1 `client_type` 固定枚举: 1. `web_browser` 2. `wechat_h5` 3. `mini_program` 4. `native_app` 5. `desktop_app` 6. `unknown` ### 6.2 `client_runtime` 当前阶段固定允许: 1. `chrome` 2. `edge` 3. `safari` 4. `firefox` 5. `wechat_embedded_browser` 6. `wechat_mini_program` 7. `alipay_mini_program` 8. `douyin_mini_program` 9. `unknown` 后续如扩展,不改已有语义,只追加枚举。 ### 6.3 `client_platform` 当前阶段固定允许: 1. `windows` 2. `macos` 3. `linux` 4. `ios` 5. `android` 6. `unknown` ## 7. 前端采集输入 ### 7.1 Web 浏览器 浏览器侧建议补传以下请求头: 1. `x-client-type` 2. `x-client-runtime` 3. `x-client-platform` 4. `x-client-instance-id` 其中: 1. `x-client-instance-id` 由前端首次启动时生成并持久化到本地存储 2. `x-client-runtime` 可由前端粗判,也允许后端根据 UA 覆盖修正 ### 7.2 小程序 小程序侧额外补传: 1. `x-client-type=mini_program` 2. `x-client-runtime` 如 `wechat_mini_program` 3. `x-client-platform` `ios` / `android` 4. `x-client-instance-id` 5. `x-mini-program-app-id` 6. `x-mini-program-env` ## 8. 后端判定规则 ### 8.1 输入优先级 Axum 侧按以下顺序解析: 1. 显式请求头 2. `User-Agent` 自动识别 3. 回退默认值 ### 8.2 默认回退 如果没有任何显式端侧头: 1. `client_type = web_browser` 2. `client_runtime` 由 UA 粗判,判不出则 `unknown` 3. `client_platform` 由 UA 粗判,判不出则 `unknown` ### 8.3 微信内 H5 如果 UA 命中 `MicroMessenger` 且不是小程序显式来源: 1. `client_type = wechat_h5` 2. `client_runtime = wechat_embedded_browser` ### 8.4 小程序来源优先于 UA 如果 `x-client-type=mini_program`: 1. 不再按普通 UA 逻辑覆盖成浏览器 2. `client_runtime` 必须优先保留小程序来源值 ## 9. `client_instance_id` 规则 ### 9.1 作用 它用于区分: 1. 同设备不同浏览器 2. 同浏览器不同安装实例 3. 小程序容器与普通 H5 容器 ### 9.2 生成策略 前端首次启动时生成一个随机字符串并持久化: 1. Web:`localStorage` / `indexedDB` 2. 小程序:平台侧本地存储 说明: 1. 清浏览器数据后会变化,这是可接受的。 2. 它不是登录凭据,只是会话识别辅助字段。 ## 10. `device_fingerprint` 规则 后端可基于以下字段做稳定哈希: 1. `client_type` 2. `client_runtime` 3. `client_platform` 4. `client_instance_id` 如果 `client_instance_id` 缺失,则回退: 1. `client_type` 2. `client_runtime` 3. `client_platform` 4. `normalized user_agent` 说明: 1. 这个指纹只用于显示和聚类,不作为鉴权依据。 2. 同一设备上的不同浏览器通常应产生不同指纹。 ## 11. `device_display_name` 派生规则 后端固定按以下优先级派生: 1. 小程序: - `微信小程序 / iPhone` - `微信小程序 / Android` - `支付宝小程序 / Android` 2. 微信内 H5: - `微信内网页 / iPhone` - `微信内网页 / Android` 3. 普通浏览器: - `Windows / Chrome` - `macOS / Safari` - `iPhone / Safari` - `Android / Chrome` 4. 无法识别时: - `未知设备` ## 12. 与会话列表 DTO 的关系 当前 `AuthSessionSummary` 至少建议新增: 1. `clientRuntime` 2. `clientPlatform` 3. `deviceDisplayName` 4. `miniProgramAppId` 当前已有字段中的: 1. `clientType` 保留,但升级为新枚举 2. `clientLabel` 后续可由 `deviceDisplayName` 替代,或先兼容保留 ## 13. 首版 Rust 落地范围 当前首版建议先完成: 1. Axum 从请求头 + UA 解析 `SessionClientContext` 2. `module-auth` 的 `refresh_session` 增加结构化客户端字段 3. 登录创建 session 时写入这些字段 4. 单元测试覆盖浏览器、小程序、微信 H5 的识别规则 本轮不强制一起完成: 1. `/api/auth/sessions` 2. 前端全面补传 `x-client-*` 头 3. 小程序端 SDK 对接 ## 14. 不允许的设计漂移 后续实现时禁止: 1. 继续把所有终端都写成 `browser` 2. 用 `User-Agent` 文本直接充当最终设备名称 3. 用 IP 作为设备唯一标识 4. 把 `client_instance_id` 当成安全凭据 5. 前端任意上传自由文本设备名并直接持久化 ## 15. 完成定义 满足以下条件时,这条能力设计视为完成: 1. 会话身份字段已能区分来源、运行时、平台、实例 2. 同设备不同浏览器与小程序来源都有明确建模 3. `device_display_name` 已有统一派生规则 4. 后续 `/api/auth/sessions` 已有稳定可用的数据基础