Files
nexus/Project/fonrey/TECH_STACK/登录管理技术方案.md
2026-05-02 11:35:20 +08:00

14 KiB
Raw Blame History

For AI assistants: Read this entire file before writing any code. All decisions here are final. Do not suggest alternatives unless asked.

Fonrey 登录管理技术方案

版本: 4.1
项目: Fonrey 房产经纪管理系统
技术栈: Django 4.x + HTMX + Alpine.js + PostgreSQL 16 + Redis + Celery
关联 PRD: PRD/登录管理/用户登录管理模块PRD.mdv3.0
关联数据模型: DATA_MODEL/DATA_MODEL_LOGIN.md(本方案不重复 DDL
关联契约规范: TECH_STACK/API_CONTRACT.md(全局 API 契约权威)
关联测试规范: TECH_STACK/测试规范.mdTEST_CASES/TEST_CASES_LOGIN_MODULE.md
最后更新: 2026-05-02


变更历史

日期 变更人 变更内容
2026-04-30 Atlas 补充"变更历史"章节(文档治理)
2026-05-02 Sisyphus ADR-20260502-003 承接从 PRD v3.0 迁出的实现细节①§5.3 新增 Tenant Verify 请求/响应 JSON Schema②§5.3 新增预留 Wechat 端点 GET /api/auth/wechat/qrcode/POST /api/auth/wechat/callback/仅占位MVP 不开放);③新增 §十三 Electron 客户端约定Tenant Code 存储/Session/登录页加载/多标签页/登出/窗口关闭/强制更新);④§六 关键流程约束补全密码错误锁定阈值、滑块容差、找回密码错误次数等数值口径,统一以本文件为准

一、文档定位与边界

本文件定义登录模块的实现口径:

  1. 模块范围与职责边界
  2. API 端点(页面 / HTMX / JSON
  3. 安全策略(滑块验证、登录锁定、短信 OTP、会话
  4. Redis/Celery 运行策略
  5. 错误码与测试映射

本文件不展开数据表字段与索引。数据结构以 DATA_MODEL_LOGIN.md 为唯一权威。


二、范围定义(以 PRD v2.0 为准)

2.1 P0 必须覆盖

  • Tenant Code 识别(首次启动 + 切换公司)
  • 密码登录(手机号/密码 + 滑块)
  • 首次登录强制修改密码
  • 找回密码(纯短信三步流程)
  • 手机验证码登录MVP 正式功能)
  • 登录失败锁定 / 自动解锁 / 管理员解锁
  • 安全登出与会话失效

2.2 非目标 / 预留

  • 微信扫码登录(仅保留禁用入口与接口占位,不开放功能)
  • 企业 SSOOAuth2 / SAML
  • 风险评分/设备指纹

2.3 已废弃(不得实现)

  • 找回用户名流程Story 4 已废弃)

三、模块架构边界

3.1 模块职责(apps/account

  • 租户识别与租户上下文建立前置校验
  • 登录鉴权、会话签发、登出销毁
  • 首登改密门禁(is_initial_password
  • 短信 OTP 发送/校验(找回密码 + 验证码登录)
  • 登录与安全审计事件写入

3.2 多租户分层职责

层级 Schema 职责
Tenant Code 校验 Public 校验 tenant_code、租户状态、品牌信息返回
登录认证 Tenant 账号鉴权、失败计数、锁定状态、会话签发
短信 OTP Tenant OTP 记录、场景区分、过期与尝试次数控制

3.3 外部依赖

依赖模块 用途
apps/org 员工状态联动(离职/停用不可登录)
core/encryption.py 手机号加密/哈希能力
core/cache.py 滑块票据、频控、锁定计数、重置 token
Celery 短信发送异步化(可选,建议)

四、API 设计原则

  1. 登录安全链路:Tenant 校验 → 滑块验证 → 登录提交
  2. 防枚举:账号不存在/停用等敏感状态采用统一外显文案。
  3. 账号锁定是账号维度策略,不区分密码登录或验证码登录。
  4. Redis 仅作运行态与频控,最终状态以数据库持久化字段为准。
  5. 所有登录相关接口遵循 TECH_STACK/API_CONTRACT.md 的统一错误响应格式。

五、端点清单

5.1 页面路由SSR

路径 方法 鉴权 说明
/auth/tenant/identify/ GET Tenant 识别页
/auth/login/ GET 登录页(密码登录/验证码登录 Tab
/auth/password/forgot/ GET 找回密码三步页
/auth/password/change-initial/ GET 首次登录强制改密页

说明:/auth/wechat/* 仅预留,不在 MVP 开放。

5.2 HTMX 片段端点

路径 方法 用途 返回
/auth/fragments/captcha/ GET 刷新滑块区块 HTML 片段
/auth/fragments/login-form/ GET 登录 Tab 内容局刷 HTML 片段
/auth/fragments/forgot-step/ GET 找回密码步骤局刷 HTML 片段

5.3 JSON APIMVP

端点 方法 说明
/api/auth/tenant/verify/ POST Tenant Code 校验(公开接口)
/api/auth/captcha/ GET 获取滑块拼图验证码(返回背景图 Base64 + 碎片图 Base64 + 验证 Token
/api/auth/captcha/verify/ POST 校验滑块并签发 captcha_pass_token
/api/auth/login/ POST 密码登录
/api/auth/login/phone/ POST 手机验证码登录
/api/auth/recover/password/request/ POST 找回密码步骤一:发 OTP
/api/auth/recover/password/verify/ POST 找回密码步骤二:校验 OTP颁发 sms_reset_token
/api/auth/recover/password/reset/ POST 找回密码步骤三:提交新密码
/api/auth/password/change-initial/ POST 首次登录强制改密提交
/api/auth/logout/ POST 登出销毁会话

5.3.1 Tenant Verify 请求/响应 Schema

请求体:

{ "tenant_code": "202500010001" }

成功响应HTTP 200

{
  "valid": true,
  "tenant_name": "XX房产经纪有限公司",
  "tenant_logo_url": "https://cdn.fonrey.com/tenants/xxx/logo.png",
  "login_url": "https://xxx.fonrey.com/auth/login/"
}

失败响应HTTP 200业务态失败

{
  "valid": false,
  "error_code": "AUTH_TENANT_NOT_FOUND",
  "message": "识别码无效"
}

限流超限响应HTTP 429遵循 API_CONTRACT.md 统一错误格式,code = AUTH_TENANT_RATE_LIMITED

5.4 预留端点MVP 不开放)

端点 方法 状态 说明
/api/auth/wechat/qrcode/ GET 仅占位 微信扫码登录二维码获取v2 实现)
/api/auth/wechat/callback/ POST 仅占位 微信扫码回调换取系统 Tokenv2 实现)

MVP 内 URLConf 中不注册这两个路由,登录页 UI 入口以禁用态展示v2 启用时再按本表落地路由与视图。


六、关键流程约束

6.1 Tenant 识别

  • tenant_code 固定 12 位数字,前后空格自动 trim
  • 成功返回租户名称、Logo URL、登录地址
  • 失败返回:valid=false + 统一错误信息
  • 接口公开但必须限流:单 IP 每分钟 ≤ 10 次

6.2 密码登录

请求体(示例):

{
  "phone": "13800138000",
  "password": "***",
  "captcha_pass_token": "token"
}

关键规则:

  • 滑块通过后方可提交登录
  • 密码连续错误 ≥ 5 次,锁定 30 分钟
  • is_initial_password = true 时强制跳转改密页
  • 错误文案统一,不泄露账号存在性细节

6.3 找回密码(纯短信三步)

步骤一:发送 OTP

  • scene = password_reset
  • OTP 有效期10 分钟
  • 同手机号频控5 次/小时
  • 手机号不存在/停用:统一提示“如该手机号已注册,验证码将在 1 分钟内发送”

步骤二:校验 OTP

  • 正确且未过期:签发 sms_reset_token15 分钟,一次性)
  • 错误累计尝试≥5 次作废该 OTP
  • 过期:提示重新获取

步骤三:重置密码

  • 必须携带有效 sms_reset_token
  • 成功后:is_initial_password = false
  • 该用户所有会话立即失效,跳回登录页

6.4 手机验证码登录MVP 正式)

请求体(示例):

{
  "phone": "13800138000",
  "sms_code": "123456"
}

关键规则:

  • 获取登录验证码前必须先通过滑块
  • scene = login
  • OTP 有效期5 分钟
  • 同手机号频控10 次/小时(与 password_reset 独立计数)
  • OTP 错误 ≥5 次作废
  • 账号 lockeddisabled 时,验证码登录同样拒绝

七、Redis Key 规范(对齐 PRD

Key TTL 说明
captcha_token:{uuid} 3 分钟 滑块验证会话
captcha_pass:{uuid} 3 分钟 一次性通过凭证
login_fail:{tenant_id}:{username} 30 分钟 登录失败计数
sms_limit:password_reset:{phone} 1 小时 找回密码 OTP 发送频控≤5次
sms_limit:login:{phone} 1 小时 登录 OTP 发送频控≤10次
sms_reset_token:{token} 15 分钟 找回密码步骤三凭证
tenant_verify_ip:{ip} 1 分钟 Tenant 校验接口 IP 限流≤10次

八、安全与合规

  1. 密码仅允许 Django 安全哈希PBKDF2/Argon2
  2. OTP 明文不得入库,仅存哈希。
  3. 敏感字段日志脱敏手机号、token、验证码
  4. 会话过期或登出后,受保护页面必须重定向登录。
  5. HTTPS 强制,不允许明文传输降级。

九、错误码建议(登录模块)

code HTTP 中文含义
AUTH_TENANT_NOT_FOUND 400 租户识别码无效
AUTH_TENANT_RATE_LIMITED 429 Tenant 校验请求过于频繁
AUTH_CAPTCHA_INVALID 400 滑块验证失败
AUTH_INVALID_CREDENTIAL 401 手机号或密码错误
AUTH_ACCOUNT_LOCKED 423 账号已锁定
AUTH_ACCOUNT_DISABLED 403 账号已停用
AUTH_SMS_OTP_INVALID 400 短信验证码错误
AUTH_SMS_OTP_EXPIRED 400 短信验证码过期
AUTH_SMS_OTP_RATE_LIMITED 429 OTP 发送超限
AUTH_SMS_RESET_TOKEN_INVALID 400 重置凭证无效或过期
AUTH_PASSWORD_WEAK 400 新密码不满足强度规则

十、测试映射(与全局编号对齐)

  • 测试编号规范:TEST_CASES/TEST_CASE_ID_SPEC.md
  • 注册表:TEST_CASES/TEST_CASE_REGISTRY.md
  • 登录模块用例:TEST_CASES/TEST_CASES_LOGIN_MODULE.md

建议最小执行集:

  • Tenant 识别:TC-FON-000001 ~ 000010
  • 密码登录与锁定:TC-FON-000011 ~ 000028
  • 首登改密:TC-FON-000029 ~ 000033
  • 找回密码:TC-FON-000034 ~ 000044
  • 验证码登录:TC-FON-000045 ~ 000048

十一、落地顺序建议

  1. Tenant 识别 + 密码登录主链路
  2. 滑块与失败锁定
  3. 首登改密门禁
  4. 找回密码三步(短信)
  5. 手机验证码登录scene=login
  6. 报表与监控补齐

十二、文档同步规则

  • PRD 登录模块变更:同步本文件
  • 数据结构调整:同步 DATA_MODEL_LOGIN.md
  • 测试用例新增/变更:同步 TEST_CASES/TEST_CASE_REGISTRY.md 与登录用例文档
  • API 契约调整:同步 TECH_STACK/API_CONTRACT.md

十三、Electron 客户端约定(实现口径)

承接自原 PRD §5.7(按 ADR-20260502-003 迁入本文件)。本节为 Electron 渲染层与主进程在登录链路上的实现规约,开发时必须严格遵循。

13.1 存储与会话

约定项 实现规格
Tenant Code 存储 electron-storeapp.getPath('userData') 下的配置文件,必须 AES 加密,禁止明文落盘
Session Token 存储 内存(主进程 global 变量)+ Chromium 管理的 session CookieHttpOnly + Secure + SameSite=Strict禁止写入磁盘明文文件
多标签页 同一 BrowserWindow 内,所有页面共享同一 session.defaultSession,复用同一 Session Cookie
窗口关闭 Session 保留(不自动登出);下次启动客户端时,若 Cookie 未过期且 /api/auth/whoami/ 校验通过,直接进入系统

13.2 登录页加载流程

  1. 主进程读取本地缓存的 tenant_code
    • 不存在 → BrowserWindow.loadURL('app://tenant-identify')(本地静态页或专用 Tenant 识别 URL
    • 存在 → 调用 POST /api/auth/tenant/verify/ 复核(防止租户被禁用 / 改名)
      • 成功 → 构建 https://{tenant_slug}.fonrey.com/auth/login/,通过 BrowserWindow.loadURL() 加载
      • 失败 → 清除本地 tenant_code 缓存 → 跳回 Tenant 识别页
  2. 切换公司:用户在登录页点击「切换公司」 → 主进程清缓存 → 关闭当前 BrowserWindow → 重新走步骤 1。

13.3 登出与强制更新

场景 客户端动作
用户登出 调用 POST /api/auth/logout/ → 清除 Chromium Session Cookiesession.defaultSession.clearStorageData({ storages: ['cookies'] }))→ 跳转登录页
服务端 Session 过期401 渲染层捕获 401 → 主进程清 Cookie → 跳转登录页并提示"登录已过期,请重新登录"
客户端版本低于 min_required_version 加载登录页前先展示"请更新客户端"模态,阻断登录流程;联动 平台管理后台技术方案.md 客户端发布章节

13.4 安全约束(与 AGENTS.md §5 对齐)

  • 渲染进程必须 nodeIntegration: falsecontextIsolation: truesandbox: true
  • 渲染层禁止内嵌业务逻辑或本地数据库(壳应用原则);
  • 主进程与渲染层之间通过 contextBridge 暴露的最小白名单 IPC 接口通信;
  • 仅允许加载 https://*.fonrey.comapp:// 协议,其他 URL 在 will-navigate 中拦截。