# 登录后接口校验与登出子进程排查报告 ## 一、排查结论概览(已改造后) | 维度 | 结论 | 说明 | |------|------|------| | 接口是否校验登录 | 是 | 需登录的接口由**后端**统一校验 session(选品配置/选品开始已改用 `verify_session_with_machine_and_expiry`) | | 接口是否校验机器码 | 是 | 心跳、选品配置、选品开始等接口均要求传 `machine_code` 并与 session 绑定一致,否则 403 | | 接口是否判断用户过期 | 是 | 上述接口在后端统一校验用户卡密/订阅未过期,过期返回 403 | | 发生上述情况是否退出登录 | 是 | 客户端对 401/403 统一走 `handleAuthError` → 退登并跳转登录页 | | 退出登录是否关闭子进程 | 是 | 登出时已调用 `stop_all_processes()`,会尝试远程停止选品与扫货任务(`selection-stop` / `sweep-stop`),失败仅记录日志不阻断登出 | --- ## 二、机器码校验:客户端传什么、后端怎么校验 ### 客户端传给后端的数据 - **来源**:`authStore.machineCode` 来自 Rust `get_machine_code()`(即 `machine::generate_machine_code()`)。 - **格式**:**原始机器特征字符串(raw)**,当前为 `DISK=<硬盘序列号>`(例如 `DISK=WD-WMAZA1234567`)。多盘时取第一个磁盘序列号;不含 MAC,避免换网卡/IP 导致同机不同码。详见 `client-tauri/src-tauri/src/machine.rs`。 ### 加密接口(心跳、keys/sync、user/profile) - 请求体为 **AES+RSA 加密**,解密后的 params 里带 **raw_machine_info**(或 machine_code)即上述 raw 字符串。 - 后端**不信任客户端传入的 machine_code 字面值**:从解密参数里取 `raw_machine_info` / `machine_code`,用 `raw_machine_info_to_machine_code(raw)` 做**规范化 + MD5**,得到 32 位小写 hex,再与 **session.machine_code**(登录时存的也是该 MD5)比较。 ### 明文接口(选品配置、filter-options、goods-attr-list、selection-start) - 客户端在 URL 或 body 里传的 **machine_code** 即为上面的 **raw**(`authStore.machineCode` = `get_machine_code()`)。 - 后端 **session 里存的是 MD5**(登录时由后端对 raw 做规范化+MD5 后写入)。 为统一校验逻辑,`verify_session_with_machine_and_expiry` 会对入参做**统一规范化**: - 若传入已是 **32 位小写 hex**,视为已 MD5,直接与 `session.machine_code` 比较; - 否则视为 **raw**(如 `DISK=xxx`),先 `raw_machine_info_to_machine_code(mc)` 得到 MD5,再与 `session.machine_code` 比较。 这样无论客户端传 raw 还是 MD5,后端都能与登录时绑定的机器码一致校验。 --- ## 三、后端校验情况(改造后) - **登录/心跳**:与改造前一致,完整校验会话、机器码、用户状态、用户卡密过期。 - `auth_service.login`、`heartbeat_service.heartbeat`。 - **选品配置与选品开始**(已改造): - `AuthService.verify_session_with_machine_and_expiry(session_id, machine_code)`:统一校验**会话有效 + 机器码与 session 一致 + 用户未禁用 + 用户卡密未过期**。 - 使用该方法的接口:`POST/GET /buff/selection-config`、`POST /buff/selection-start`,请求体/查询参数均要求传 `session_id` 与 `machine_code`。 - 不通过时返回 401(会话过期/用户不存在)或 403(机器码不匹配/账号禁用/卡密过期),客户端会退登并跳转登录。 --- ## 四、客户端接口与 401/403 处理 ### 3.1 已统一处理 401/403(会退登并跳转登录) - `secureApiClient.login`:401/403 → `handleAuthError` - `secureApiClient.recharge`:401/403 → `handleAuthError` - `secureApiClient.unbind`:401/403 → `handleAuthError` - `secureApiClient.changePassword`:错误信息含 401/403/会话 → `handleAuthError` - `secureApiClient.heartbeat`:401/403 或 catch 到会话相关错误 → `handleAuthError` - `secureApiClient.getProfile`:401/403 → `handleAuthError` - **本次新增**:`secureApiClient.getBuffSelectionConfig`:401/403 → `handleAuthError` - **本次新增**:`secureApiClient.saveBuffSelectionConfig`:401/403 → `handleAuthError` ### 3.2 无需登录或无需退登的接口 - `getAnnouncements`:公告接口,未做 401/403 退登(按设计可为公开)。 - `getClientConfigs`:后端为“无需登录”的配置接口,未做 401/403 退登。 - `getBuffFilterOptions` / `getBuffGoodsAttrList`:已改为需登录,后端校验 session + machine_code + 用户过期,前端传参并做 401/403 → `handleAuthError`。 - `syncHmacKey`(密钥同步):后端已校验用户+机器码+卡密过期,前端 401/403 → `handleAuthError`。 ### 3.3 登出与事件 - `handleAuthError` 会:停止心跳、调用 `authStore.logout()`、派发 `auth-error`,由 `App.vue` 统一 `router.replace('/login')`。 - 应用启动时若“会话预检”(心跳)失败,也会退登并跳转登录。 --- ## 五、登出时是否关闭子进程 - **Rust 侧**:`user_logout` 顺序为: 1. 调用 `stop_all_processes()`(停止客户端 exe 开启的子进程,如选品、扫货); 2. 调用 `stop_heartbeat_service()`; 3. 调用后端 logout 接口。 - **当前**:`stop_all_processes()` 已实现并在登出时被调用,内部会调用后端安全接口停止选品与扫货任务(`/api/v1/buff/secure/selection-stop`、`/api/v1/buff/secure/sweep-stop`)。 - **建议**:保持“尽最大努力停止”策略(失败不阻断登出),并持续完善停止链路的可观测日志,便于问题追踪。 --- ## 六、本次代码修改摘要 ### 后端 (backend-client) 1. **app/services/auth_service.py** - 新增 `verify_session_with_machine_and_expiry(session_id, machine_code, software_code=None)`:校验会话存在且未过期、请求机器码与 session 一致、用户存在且未禁用、用户卡密未过期;返回 `(session, err_code, err_msg)`,失败时为 401/403。 2. **app/api/v1/router.py** - `POST /buff/selection-config`:请求体增加必填 `machine_code`,改用 `verify_session_with_machine_and_expiry`,不通过返回 401/403。 - `GET /buff/selection-config`:查询参数增加必填 `machine_code`,同上。 - `POST /buff/selection-start`:请求体增加必填 `machine_code`,同上。 ### 前端 (client-tauri) 3. **src/utils/secureApiClient.ts** - `getBuffSelectionConfig(session_id, machine_code)`:增加参数 `machine_code`,请求 URL 带 `machine_code`;响应 401/403 时调用 `handleAuthError`。 - `saveBuffSelectionConfig`:参数增加必填 `machine_code`,请求体带 `machine_code`;响应 401/403 时调用 `handleAuthError`(此前已加)。 4. **src/views/BuffSelectionView.vue** - 加载/保存选品配置时从 `authStore.machineCode` 取值,调用 `getBuffSelectionConfig(sessionId, machineCode)` 与 `saveBuffSelectionConfig(..., machine_code: machineCode)`。 5. **src-tauri/src/commands.rs** - `user_logout`:在停止心跳之前先调用 `stop_all_processes().await`(错误仅打日志,不阻断登出),保证登出时尝试关闭子进程。 ### 七、补充改造(登录后全部业务接口三项校验) - **心跳、密钥同步(keys/sync)、用户信息(user/profile)**:已通过 `@secure_endpoint(require_login=True, require_machine_code=True)` 做会话+机器码+用户状态+**用户卡密过期**校验,不通过返回 401/403。 - **HMAC 密钥同步(keys/hmac)**:后端 `validate_user_for_hmac` 增加**用户卡密/订阅过期**校验,过期返回 403;前端 `syncHmacKey` 在 401/403 时调用 `handleAuthError` 触发退登。 - **BUFF 筛选项(filter-options)、商品属性(goods-attr-list)**:后端增加必填 `session_id`、`machine_code`,使用 `verify_session_with_machine_and_expiry` 做三项校验;前端传参并在 401/403 时 `handleAuthError`,选品页调用处传入 `authStore.sessionId`、`authStore.machineCode`。 --- *文档生成日期:2026-03-01;改造完成:选品相关接口已统一“登录+机器码+过期”校验并触发退登,登出时已调用 stop_all_processes;登录后业务接口(含心跳、密钥交换、选品)均已三项校验且 401/403 退登。*