Files
qiming/qimingclaw/crates/agent-electron-client/docs/architecture/auth-savedkey-lifecycle.md

6.7 KiB
Raw Permalink Blame History

version, last-updated, status
version last-updated status
1.1 2026-03-19 stable

认证机制与 SavedKey 生命周期

本文档说明 Qiming Agent 客户端的认证设计,重点描述 savedKey 的作用、存储结构、完整生命周期,以及退出登录与服务启动的联动逻辑。


核心概念

两类认证凭证

凭证 存储键 说明 退出登录后
configKey auth.config_key 当前会话的登录态 token从服务端 /register 接口获取 清除
savedKey auth.saved_key 设备级注册凭证("记住我" token与 configKey 值相同,但跨登录会话持久化 保留

重要savedKeyconfigKey 值相同(均为服务端返回的 response.configKey),区别在于持久化策略不同。savedKey 的存在代表"这台设备已完成注册,可以免密重新认证"。

多账号隔离存储

savedKeydomain + username 维度分别存储,支持同一设备多账号切换:

auth.saved_key                          ← 全局快速访问(最后一次登录的账号)
auth.saved_keys.<domain>_<username>     ← 域名+用户级持久化(每个账号独立)

键名与迁移:存储键中的 <domain>normalizeDomain()auth.ts生成hostname 或规范化字符串。若未来调整域名规范化规则如大小写、去端口、trailing slash需考虑旧键兼容或提供迁移逻辑避免已存账号无法匹配。


完整生命周期

第一次登录(全新设备/账号)

用户输入 domain + username + password
    │
    └── getSavedKey(domain, username)
        └── 未找到 → savedKey = undefined
            │
            └── registerClient({ username, password, savedKey: undefined })
                │
                └── 服务端返回 { configKey, ... }
                    │
                    ├── setConfigKey(configKey)
                    │       └── auth.config_key = configKey
                    │
                    └── setSavedKey(configKey, domain, username)
                            ├── auth.saved_keys.<domain>_<username> = configKey
                            └── auth.saved_key = configKey

退出登录

用户点击"退出登录"ClientPage.tsx → handleLogout
    │
    ├── 1. 停止所有运行中的服务(按顺序)
    │       ├── agent.destroy()
    │       ├── fileServer.stop()
    │       ├── lanproxy.stop()
    │       ├── mcp.stop()
    │       └── computerServer.stop()   ← 登出时单独停止,避免进程残留与端口占用
    │
    └── 2. logout() → clearAuthInfo()
            ├── auth.username      = null
            ├── auth.config_key    = null    ← 登录态清除
            ├── auth.user_info     = null
            ├── auth.online_status = null
            └── auth.saved_key     = 保留    ← 设备凭证不清除,下次可免密重连

注意:密码从不持久化保存。登录时用户输入的密码仅用于本次认证请求,不存入数据库。

重新打开 App有 savedKey自动重连

App 启动 → autoReconnectApp.tsx
    │
    ├── 检查 loginStartedRef.current内存变量
    │       └── 已由登录流程启动 → 跳过(同一会话内有效)
    │
    ├── 检查 setupJustCompleted
    │       └── 向导刚完成 → 直接启动服务(不走重连)
    │
    └── 读取 auth.saved_key
            ├── 不存在 → 跳过(首次使用,需手动登录)
            │
            └── 存在 → syncConfigToServer({ suppressToast: true })
                    │
                    ├── 成功(服务端接受 savedKey
                    │       ├── 更新 auth.config_key新 token
                    │       ├── 更新 auth.saved_key续期
                    │       ├── 更新 auth.online_status / user_info
                    │       └── startServicesSequentially(
                    │               ['mcpProxy', 'agent', 'fileServer', 'lanproxy']
                    │           )
                    │
                    └── 失败(网络断开 / token 过期 / 账号被封)
                            └── 服务不启动,停留在登录页

用户再次登录(已有 savedKey 的账号)

用户输入 domain + username + password
    │
    └── getSavedKey(domain, username)
        └── 找到 → savedKey = "<持久化的 configKey>"
            │
            └── registerClient({ username, password, savedKey })
                    └── 服务端识别为老设备,直接续期返回新 configKey

切换账号(不同 domain 或 username

用户输入 domainB + userB与上次不同
    │
    └── getSavedKey(domainB, userB)
        └── 未找到 → savedKey = undefined
            │
            └── registerClient({ username, password, savedKey: undefined })
                    └── 服务端视为全新设备注册

服务与登录状态的联动规则

场景 服务行为
当前会话退出登录 立即停止所有运行中的服务
重启 App有 savedKey重连成功 自动启动全部服务mcpProxy → agent → fileServer → lanproxy
重启 App有 savedKey重连失败 服务不启动,停留在登录页
重启 App无 savedKey 服务不启动,停留在登录页
手动登录成功 onComplete 触发服务启动(不走 autoReconnect

服务启动顺序固定mcpProxy 必须先于 agent,因为 Agent 初始化时需要连接 MCP Proxy 注入 mcpServers。


相关代码位置

逻辑 文件 关键函数/位置
savedKey 存取 src/renderer/services/core/auth.ts getSavedKey() / setSavedKey() L86-100
退出登录清理 src/renderer/services/core/auth.ts clearAuthInfo() L131-138
停止服务+登出 src/renderer/components/pages/ClientPage.tsx handleLogout() L171-200
自动重连逻辑 src/renderer/App.tsx autoReconnect effect L297-349
静默重新认证 src/renderer/services/core/auth.ts syncConfigToServer() L383-458
注册/登录 src/renderer/services/core/auth.ts loginAndRegister() L209-314

相关文档