Files
nexus/Project/fonrey/PRD/登录管理/用户登录管理模块PRD.md

39 KiB
Raw Blame History

PRD用户登录管理模块

状态: Draft
作者: 产品经理
最后更新: 2026-04-24v1.3 明确账号创建权限层级Tenant Admin 可自定义用户名/密码;普通员工账号由 Tenant Admin 在新增员工时创建,用户名为手机号,初始密码固定,首次登录强制修改)
版本: 1.3
所属系统: Fonrey 房产经纪管理系统
关联模块: 组织人事管理、权限管理、系统管理


1. 问题陈述

1.1 背景

Fonrey 是一套面向房产经纪公司的 B2B SaaS 平台,采用多租户架构(django-tenants + PostgreSQL Schema 隔离)。终端用户通过 Windows 桌面客户端Electron 使用系统,无需手动输入网址即可打开 Web 应用。

在多租户环境下,用户的身份验证流程比单租户系统更复杂:

  • 用户安装客户端后,系统必须先识别当前设备归属哪个租户,才能加载对应租户的登录界面和数据隔离环境
  • 每家经纪公司作为独立租户,其员工账号、组织结构、数据均完全隔离
  • 经纪人账号须与实名员工档案绑定,确保每一条操作记录可追溯至具体自然人
  • 现阶段登录方式以账号密码为主;手机验证码登录、微信扫码登录需预留接口,待移动端(小程序)上线后实现

1.2 核心痛点

痛点 影响方 当前代价
多租户环境下,客户端不知道应该连接哪个租户的服务端 新用户首次安装后无法正常使用 系统无法启动,用户体验极差
账号密码裸露登录,缺乏验证码保护 所有用户 存在暴力破解、自动化恶意登录风险
用户忘记账号或密码无自助找回通道 一线经纪人 依赖管理员手动重置,效率低
账号未与实名经纪人档案绑定 系统管理员、合规审计 操作行为无法追溯至自然人
无多因素认证,安全系数低 管理层、数据合规 存在账号冒用、数据泄露风险

1.3 目标用户

角色 描述 使用频率
一线经纪人 每日登录系统使用房源/客源功能 每日高频
店长 / 经理 登录后查看全店数据、管理任务 每日
系统管理员 管理账号创建、密码重置、租户初始化 按需
新安装用户 首次安装客户端后需完成 Tenant 识别 一次性

2. 目标与成功指标

目标 指标 当前基准 目标值 衡量周期
降低登录失败率 账号密码正确情况下的登录成功率 待统计 ≥ 99% 上线后 30 天
防止恶意登录 每日验证码拦截异常登录请求数 0无保护 建立基线同IP异常次数 > 5次/分钟触发封锁 上线后持续监控
提升找回账号效率 用户自助找回密码耗时 依赖管理员约1工作日 < 3 分钟(邮件/短信自助) 上线后 30 天
确保账号实名绑定率 拥有系统账号且未与员工档案绑定的账号比例 待统计 0%(强制绑定) 上线即达标
Tenant 识别成功率 首次安装后成功完成 Tenant 识别的用户比例 待统计 ≥ 98% 上线后 30 天

3. 非目标(本期不做)

  • 手机验证码登录:移动端小程序上线后实现,本期接口预留UI 入口以「即将开放」禁用态展示
  • 微信扫码登录:移动端小程序上线后实现,本期接口预留UI 入口以「即将开放」禁用态展示
  • 单点登录SSO/ 企业微信集成:后续版本规划
  • 多设备并发登录的强制踢出策略:本期允许同账号多端登录,后续安全策略模块规划
  • 登录时段限制 / IP 白名单:安全策略模块另行规划
  • 管理后台Platform Admin登录:系统管理员登录管理后台的流程属于系统管理模块,本 PRD 专注租户内用户登录

4. 用户故事与验收标准


Story 1新用户首次启动客户端——Tenant 识别

As 新安装 Fonrey 客户端的经纪人,I want 在首次启动时输入所属公司的 Tenant ID 完成租户识别,So that 客户端能连接到正确的服务端,后续显示对应公司的登录界面和数据。

验收标准

  • 客户端首次启动时(本地无 Tenant ID 缓存自动呈现「Tenant 识别」界面,而非直接显示登录界面
  • 界面包含:产品 Logo、产品名称「Fonrey 房睿」、说明文案「请输入您公司的专属识别码」、Tenant ID 输入框、「确认」按钮
  • Tenant ID 输入框支持粘贴操作,自动去除前后空格
  • 点击「确认」后,客户端向服务端发起 Tenant 验证请求(POST /api/auth/tenant/verify/展示加载状态spinner
  • 验证成功服务端返回租户名称及品牌信息如公司名称、Logo URL客户端将 Tenant ID 写入本地持久化存储自动跳转至该租户的登录界面界面顶部展示「正在登录XX 房产」
  • 验证失败Tenant ID 无效)输入框下方显示红色错误提示「识别码无效请联系您的系统管理员获取正确的识别码」Tenant ID 不写入本地缓存;用户可重新输入
  • 网络异常:显示「网络连接失败,请检查网络后重试」,提供「重试」按钮
  • 非首次启动(本地已有合法 Tenant ID 缓存):直接跳过识别界面,进入登录界面
  • 登录界面提供「切换公司」入口(链接文字,非主要 CTA点击后清除本地 Tenant ID 缓存并重新显示 Tenant 识别界面;确认前弹出二次确认「切换公司将退出当前账号,是否继续?」
  • Tenant 验证接口属于公开接口,无需鉴权;但需对单 IP 请求频率限制(每分钟 ≤ 10 次)以防止枚举攻击

Story 2经纪人通过账号密码登录

As 已识别租户的经纪人,I want 通过用户名和密码完成登录,So that 进入系统开始工作。

验收标准

  • 登录界面展示:租户品牌标识(公司 Logo + 公司名称)、用户名输入框、密码输入框、滑块拼图验证区域、「登录」按钮
  • 用户名输入框 Placeholder「请输入用户名」支持英文字母、数字、下划线最大长度 50 字符
  • 密码输入框默认密文显示,右侧提供「显示/隐藏」图标切换明密文
  • 行为验证码(滑块拼图):展示一张带缺口的背景图和一块可拖动的拼图碎片,用户通过拖动滑块将碎片移动至缺口位置完成验证;无需输入任何字符,操作直观快速
  • 验证逻辑:前端记录滑动轨迹(坐标序列 + 耗时),与背景图缺口位置一同发送至服务端;服务端综合校验位置偏差(允许 ±5px 容差)和轨迹特征(是否存在人类滑动的加速/减速规律)以区分机器行为
  • 验证失败(位置不准或轨迹异常):拼图区域抖动动画提示失败,自动刷新新的背景图,用户重新拖动;不计入账号密码错误次数
  • 验证成功后,拼图区域显示绿色对勾 + 「验证通过」文案,状态持续至本次登录提交完成
  • 提供「刷新」图标按钮,允许用户主动刷新背景图(针对图片模糊或缺口不清晰的情况)
  • 背景图从预置图库中随机抽取,缺口位置每次随机生成,防止固定模式被预测
  • 三项(用户名、密码、验证码)均有填写后,「登录」按钮才可点击(否则置灰)
  • 点击「登录」触发前端格式校验:
    • 用户名为空 → 输入框下方红色提示「请输入用户名」
    • 密码为空 → 提示「请输入密码」
    • 验证码为空 → 提示「请输入验证码」
  • 格式校验通过后,向服务端发起登录请求,按钮进入 loading 状态防止重复提交
  • 登录成功:服务端返回 Session Token客户端存储 Token跳转至系统首页顶部显示欢迎信息「欢迎回来{姓名}」
  • 登录失败(用户名或密码错误):显示「用户名或密码错误,请重新输入」(不区分是用户名错误还是密码错误,防止枚举攻击);验证码自动刷新;密码输入框清空;用户名保留
  • 登录失败(验证码错误):显示「验证码有误,请重新输入」;验证码自动刷新;验证码输入框清空
  • 账号被锁定(同一账号密码连续错误 ≥ 5 次):显示「账号已被临时锁定,请 30 分钟后重试,或联系管理员解锁」;锁定状态下「登录」按钮置灰
  • 账号已停用:显示「账号已停用,请联系您的管理员」
  • Session 过期:用户在系统内操作时 Session 过期,自动跳转至登录界面,并提示「登录已过期,请重新登录」
  • 登录界面底部提供:「忘记用户名」链接、「忘记密码」链接(详见 Story 3、Story 4

Story 3经纪人找回用户名

As 忘记用户名的经纪人,I want 通过绑定的邮箱或手机号找回用户名,So that 不依赖管理员也能自助恢复登录。

验收标准

  • 点击登录界面「忘记用户名」链接,跳转至「找回用户名」页面(或弹窗)
  • 找回方式(本期以邮箱为主,手机号为预留字段):
    • 邮箱找回:输入注册邮箱,系统校验邮箱是否与已知账号匹配,匹配成功则发送包含用户名的邮件至该邮箱
    • 手机号找回预留UI 入口以「即将开放」禁用态展示)
  • 邮箱输入框:格式校验(包含「@」和域名),错误时提示「请输入有效的邮箱地址」
  • 点击「发送」后:
    • 邮箱存在且已绑定账号 → 显示「用户名已发送至您的邮箱,请查收」;发送按钮进入 60 秒倒计时不可重复点击
    • 邮箱不存在 → 不提示「邮箱未注册」(防止用户信息枚举),统一显示「如该邮箱已绑定账号,您将收到一封包含用户名的邮件」
  • 邮件内容:纯文本邮件,包含用户名、发送时间,及「如非本人操作请联系管理员」说明
  • 发送频率限制:同一邮箱 1 小时内最多发送 3 次
  • 提供「返回登录」链接

Story 4经纪人找回密码

As 忘记密码的经纪人,I want 通过已知用户名 + 绑定邮箱(或手机号)自助重置密码,So that 不依赖管理员也能快速恢复登录。

验收标准

  • 点击登录界面「忘记密码」链接,跳转至「找回密码」流程(分步骤页面或 Stepper 组件)

步骤一:身份验证

  • 用户输入:用户名 + 邮箱(本期);手机号找回为预留入口(禁用态)
  • 服务端校验用户名与邮箱是否匹配,不泄露具体原因(统一提示「如信息匹配,重置链接将发送至您的邮箱」)
  • 校验通过后,向绑定邮箱发送含一次性重置链接的邮件;链接有效期 30 分钟,使用后立即失效
  • 同一账号 1 小时内最多发送 3 次重置邮件

步骤二:重置密码

  • 用户点击邮件中的链接,跳转至「重置密码」页面(链接含加密 Token服务端校验 Token 有效性)
  • Token 无效或已过期 → 显示「链接已过期或已使用,请重新申请」,提供「重新申请」按钮
  • 页面包含:新密码输入框、确认新密码输入框
  • 密码复杂度规则(符合安全基线):
    • 长度 8 ~ 32 位
    • 必须包含字母(区分大小写)和数字
    • 建议包含特殊符号(非强制,但页面提示推荐)
    • 不得与最近 3 次历史密码相同
  • 两次密码输入不一致 → 提示「两次密码输入不一致」
  • 不符合复杂度 → 实时提示具体不满足的规则(逐条校验,红色 × / 绿色 ✓ 视觉指引)
  • 提交成功 → 显示「密码已重置,请使用新密码登录」,自动跳转至登录界面;原所有 Session 立即失效(强制重新登录)

Story 5预留——手机验证码登录接口预留v2 实现)

As 绑定了手机号的经纪人,I want 通过手机号 + 短信验证码快速登录,So that 在忘记密码时仍能正常登录系统。

当前状态:本期 UI 入口以「即将开放」禁用态展示于登录界面,接口定义预留,不开放实际功能。

预留接口设计(供后端提前规划):

POST /api/auth/login/phone/
Request: { phone: string, sms_code: string, tenant_id: string }
Response: { token: string, user: {...} } | { error: string }

绑定条件v2 实现时的前置要求):

  • 手机号必须先在「个人设置」中与用户名账号完成绑定并通过验证
  • 一个手机号只能绑定一个用户名账号(同一租户内)
  • 绑定手机号后,可通过手机号 + 短信验证码联合登录

Story 6预留——微信扫码登录接口预留v2 实现)

As 绑定了微信账号的经纪人,I want 在登录界面扫描微信二维码完成登录,So that 免去输入账号密码的步骤,提升登录体验。

当前状态:本期 UI 入口以「即将开放」禁用态展示于登录界面,接口定义预留,不开放实际功能。

预留接口设计(供后端提前规划):

GET  /api/auth/wechat/qrcode/          # 获取微信扫码二维码(含 state + 有效期)
POST /api/auth/wechat/callback/        # 微信扫码确认后回调,换取系统 Token

绑定条件v2 实现时的前置要求):

  • 微信账号必须先在「个人设置」中与用户名账号完成绑定
  • 二维码有效期 3 分钟,过期后前端自动刷新二维码
  • 微信账号只能绑定一个用户名账号(同一租户内)

5. 功能详细说明

5.1 客户端 Tenant 识别流程

5.1.1 流程概述

客户端启动
    │
    ├─ 本地有 Tenant ID 缓存?
    │       │
    │      YES ──→ 校验缓存 Tenant ID 是否仍有效(服务端 validate
    │                   │
    │                  有效 ──→ 直接进入登录界面
    │                   │
    │                 无效 ──→ 清除缓存,进入 Tenant 识别界面
    │
    └─ NO ──→ 显示 Tenant 识别界面
                   │
               用户输入 Tenant ID → 发起验证
                   │
              验证成功 ──→ 缓存 Tenant ID → 进入登录界面
                   │
              验证失败 ──→ 显示错误信息,保持识别界面

5.1.2 Tenant 识别界面规范

元素 规格
页面背景 品牌色渐变(与登录界面保持一致的视觉风格)
Logo Fonrey 产品 Logo居中显示
标题 「欢迎使用 Fonrey 房睿」
副标题 「请输入您公司的专属识别码以继续」
Tenant ID 输入框 单行数字输入,固定 12 位,支持粘贴;非数字字符自动过滤,超出 12 位截断
输入框 Label 「公司识别码Tenant ID
确认按钮 主色调按钮,文字「确认」
错误提示 输入框下方红色文字,固定区域占位(不影响布局抖动)
帮助文案 「不知道识别码?请联系您公司的系统管理员」

5.1.3 Tenant ID 格式规范

  • 格式:固定 12 位纯数字,如 202500010001
  • 生成规则(建议):由平台运营在系统管理后台开通租户时自动生成,不允许手动指定,确保全局唯一性;可采用时间戳前缀 + 随机后缀的方式生成(如 YYYYMM + 6 位随机数)
  • 前端校验:输入框仅接受数字字符(非数字自动过滤),输入满 12 位后自动触发格式完成状态;少于 12 位时点击「确认」弹出提示「识别码须为 12 位数字」
  • 唯一性:全局唯一(公共 Schema 层面),同一 Tenant ID 不可分配给多个租户
  • 客户端存储Electron app.getPath('userData') 目录下的配置文件(加密存储,防止明文读取)

5.1.4 服务端 Tenant 验证接口规范

POST /api/auth/tenant/verify/

Request Body:
{
  "tenant_id": "202500010001"
}

Response 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/"
}

Response 200 (失败):
{
  "valid": false,
  "error_code": "TENANT_NOT_FOUND",
  "message": "识别码无效"
}

注意:该接口属于 shared_apps 范围,路由在公共 Schema 下,不需要租户鉴权,但需要限流保护(每 IP 每分钟 ≤ 10 次请求)。


5.2 登录界面设计规范

5.2.1 界面布局

┌─────────────────────────────────────────┐
│  [租户 Logo]  [租户公司名称]              │  ← 顶部品牌区Tenant 识别后回填)
│                                         │
│         ┌───────────────────────┐       │
│         │  用户名               │       │
│         └───────────────────────┘       │
│         ┌───────────────────────┐       │
│         │  密码              👁  │       │
│         └───────────────────────┘       │
│   ┌─────────────────────────────────┐   │
│   │ [背景图 + 拼图缺口]          🔄 │   │  ← 右上角刷新图标
│   │                                 │   │
│   │ [拼图碎片]                      │   │
│   │ ├────────────────────────────── │   │
│   │ ◀   拖动滑块完成拼图   ▶       │   │
│   └─────────────────────────────────┘   │
│         ┌───────────────────────┐       │
│         │       登  录           │       │  ← 主 CTA橙色
│         └───────────────────────┘       │
│                                         │
│  忘记用户名     忘记密码                 │  ← 次级入口,文字链接
│                                         │
│  ─────────────── 其他登录 ──────────────│
│  [手机验证码登录 - 即将开放]              │  ← 禁用态,灰色
│  [微信扫码登录 - 即将开放]               │  ← 禁用态,灰色
│                                         │
│  切换公司                               │  ← 底部,小字链接
└─────────────────────────────────────────┘

5.2.2 安全机制

机制 规格
验证码类型 滑块拼图行为验证码:展示带缺口的背景图 + 可拖动的拼图碎片,用户滑动碎片至缺口完成验证,无需输入字符
验证逻辑 服务端综合校验位置偏差(缺口中心 ±5px 容差)+ 滑动轨迹特征(加速/减速曲线、总耗时),双重判断是否为人类行为
背景图来源 预置图库随机抽取,缺口位置每次服务端随机生成,防止固定模式被预测
验证码有效期 单次验证会话有效,提交登录后服务端 Token 立即失效;超过 3 分钟未操作需重新加载
验证失败处理 拼图区域抖动动画提示,自动刷新新背景图;不计入账号密码错误次数(行为验证失败属独立事件)
密码错误锁定 同一账号连续密码错误 ≥ 5 次,锁定 30 分钟;解锁方式:等待超时自动解锁 或 管理员手动解锁
密码错误计数 计数存于 RedisKey 格式:login_fail:tenant_id:usernameTTL 30 分钟
验证码刷新 登录失败(用户名/密码错误)后自动刷新拼图;用户亦可主动点击「刷新」图标重新加载背景图
HTTPS 所有登录相关请求强制 HTTPS不允许 HTTP 降级
密码传输 前端不做密码加密HTTPS 层保证传输安全;后端存储使用 django.contrib.auth 默认的 PBKDF2+SHA256 哈希
Session 有效期 默认 8 小时(工作日单日使用场景);可由租户管理员在「系统设置」中调整

5.3 账号与员工实名绑定规范

5.3.1 绑定原则

  • 每个系统登录账号必须与「组织人事管理」模块中的一条**员工档案Staff**绑定
  • 账号与员工是 1:1 关系,一个员工对应一个账号,一个账号只能绑定一个员工
  • 不支持用户自行注册,所有账号均由有权限的管理角色创建

5.3.2 账号创建权限分层

系统内共有两类账号创建场景,权限和规则各不相同:

① Tenant Admin 账号(每个租户唯一的超级管理账号)

项目 规格
创建时机 平台运营在系统管理后台开通租户时,同步创建第一个 Tenant Admin 账号
用户名 由平台运营自定义设置,格式:英文字母开头,仅含字母/数字/下划线6~30 字符,同租户内唯一
初始密码 由平台运营自定义设置须符合密码复杂度规则8~32 位,含字母+数字)
首次登录 强制修改初始密码,不可跳过
权限范围 拥有该租户内最高权限,可管理员工账号、角色、系统设置等
数量限制 每个租户仅限 1 个 Tenant Admin 账号后续可扩展为多管理员v2 规划)

② 普通员工账号(经纪人、店长、行政等)

项目 规格
创建时机 Tenant Admin 在「组织人事管理 → 新增员工」时,系统自动为该员工创建登录账号
用户名 固定为该员工的手机号11 位数字),同租户内唯一,创建后不可更改
初始密码 系统统一固定初始密码(由平台在部署配置中设定,如 Fonrey@2025),所有新员工账号均使用同一初始密码
首次登录 强制修改初始密码,不可跳过(详见 5.3.4
密码重置 Tenant Admin 可在员工管理界面对任意员工账号执行「重置密码」,重置后恢复为固定初始密码,触发首次登录强制修改流程
账号禁用 员工离职或被停用时,对应账号自动禁用;禁用账号无法登录,历史操作记录保留

5.3.3 账号字段规范

字段 类型 Tenant Admin 普通员工账号 说明
用户名username CharField(30) 平台运营自定义,字母开头,含字母/数字/下划线6~30 字符 固定为员工手机号11 位数字) 登录 ID创建后不可更改
密码password CharField 平台运营自定义初始密码 系统统一固定初始密码 PBKDF2+SHA256 哈希存储
手机号phone CharField(11) 选填,加密存储 必填,同时作为用户名,加密存储,同租户内唯一 当前阶段为登录 IDv2 启用手机验证码登录后复用此字段
邮箱email EmailField 选填,同租户唯一 选填,同租户唯一 用于找回密码;若为空则无法自助找回
员工档案关联staff_id OneToOneField → org.Staff 可选关联(平台运营账号) 必须关联 实名绑定
账号状态status CharField active / disabled / locked active / disabled / locked locked 为密码错误锁定30 分钟自动恢复
初始密码标记is_initial_password BooleanField True首次登录前 True首次登录前 True 时登录成功后强制跳转修改密码页
创建人created_by ForeignKey → self 平台运营(系统管理后台) Tenant Admin 审计追溯

5.3.4 首次登录强制修改密码

  • 新员工账号创建后,is_initial_password = True,账号处于「初始密码」状态
  • 员工使用手机号(用户名)+ 固定初始密码登录成功后,系统立即跳转至「修改初始密码」强制页面,不可关闭、不可跳过,任何其他系统功能页面均不可访问
  • Tenant Admin 对员工账号执行「重置密码」后,is_initial_password 重置为 True该员工下次登录时再次触发强制修改流程
  • 修改成功后,is_initial_password 更新为 False原 Session 保持有效,直接进入系统首页

强制修改密码页面规范

元素 规格
页面标题 「欢迎使用 Fonrey请先设置您的登录密码」
提示文案 「您当前使用的是初始密码,为保障账号安全,请立即设置新密码后开始使用」
新密码输入框 密文显示,右侧「显示/隐藏」切换
确认新密码输入框 密文显示,与新密码一致性实时校验
密码强度提示 逐条实时显示规则达标状态(✓/✗):长度 ≥ 8 位 / 包含字母 / 包含数字
提交按钮 「确认并进入系统」
不可操作项 无「跳过」按钮;顶部导航栏、侧边菜单、关闭按钮均禁用

5.4 找回流程详细说明

5.4.1 找回用户名流程

说明:由于普通员工的用户名即为其手机号,通常无需「找回用户名」功能。登录界面的「忘记用户名」入口保留,但仅对 Tenant Admin 账号有意义(其用户名为自定义字符串)。

用户点击「忘记用户名」
    │
    ├─ 普通员工:提示「您的登录账号为您的手机号,请直接使用手机号登录」
    │           提供「返回登录」按钮
    │
    └─ Tenant Admin用户名非手机号格式
           │
           ├─ 输入绑定邮箱
           │       │
           │   服务端查询(不向前端返回查询结果,防止枚举)
           │       │
           │   统一响应「如该邮箱已绑定账号,您将收到邮件」
           │       │
           │   后台:邮箱存在 → 发送邮件(包含用户名)
           │         邮箱不存在 → 静默处理
           │
           └─ 用户查收邮件,获取用户名 → 返回登录

前端识别逻辑:用户在「忘记用户名」页面输入邮箱提交后,服务端根据是否匹配到 Tenant Admin 账号决定处理路径,前端无需区分,统一展示「如该邮箱已绑定账号,您将收到邮件」。

邮件模板(找回用户名)

主题:您的 Fonrey 房睿用户名

您好,

您请求找回在 [公司名称] 的 Fonrey 账号用户名。

您的用户名为:{username}

如果这不是您的操作,请忽略此邮件。如有疑问,请联系您的系统管理员。

此邮件由系统自动发送,请勿回复。
发送时间:{datetime}

5.4.2 找回密码流程

用户点击「忘记密码」
    │
步骤1身份验证
    │
    ├─ 输入手机号(即用户名)+ 绑定邮箱
    │   Tenant Admin 则输入自定义用户名 + 绑定邮箱)
    │       │
    │   服务端校验用户名与邮箱是否匹配
    │       │
    │   统一响应「如信息匹配,重置链接将发送至您的邮箱」(防止枚举)
    │       │
    │   后台:匹配成功 → 生成加密 Token有效期 30min→ 异步发送邮件
    │         不匹配 → 静默处理
    │
步骤2用户点击邮件中的重置链接
    │
    ├─ 服务端校验 Token 有效性
    │       │
    │   有效 → 展示「重置密码」表单
    │       │
    │   无效/过期 → 提示「链接已过期,请重新申请」,提供「重新申请」按钮
    │
步骤3用户输入并提交新密码
    │
    ├─ 密码复杂度校验(≥ 8 位,含字母+数字)
    ├─ 与历史密码对比校验(最近 3 次,含固定初始密码)
    │
    └─ 校验通过 → 更新密码is_initial_password = False
                → 清除该账号所有有效 Session强制重新登录
                → 跳转登录界面,提示「密码已重置,请重新登录」

注意:找回密码流程依赖员工账号绑定了邮箱。若员工未绑定邮箱,无法自助找回,需联系 Tenant Admin 在管理界面执行「重置密码」操作,将密码恢复为固定初始密码。

邮件模板(重置密码)

主题:重置您的 Fonrey 房睿密码

您好,

我们收到了重置您在 [公司名称] 的 Fonrey 账号密码的请求。

请点击以下链接重置密码(链接 30 分钟内有效):
{reset_link}

如果您未发起此请求,请忽略此邮件,您的密码不会被更改。

此邮件由系统自动发送,请勿回复。
发送时间:{datetime}

5.5 后端数据模型设计

5.5.1 auth App 目录结构

在现有 fonrey/apps/ 目录下新增(或扩展 Django auth 系统):

apps/
└── accounts/               # 账号认证管理(租户级 App
    ├── models.py           # UserAccount, LoginAttempt, PasswordResetToken
    ├── views.py            # 登录/登出/找回账号/找回密码视图
    ├── urls.py
    ├── serializers.py      # API 序列化(如需 JSON 接口)
    └── services/
        ├── auth.py         # 认证逻辑(验证码校验、账号锁定判断)
        ├── recovery.py     # 找回密码/用户名逻辑
        └── tenant.py       # Tenant 验证逻辑(属于 shared_apps

5.5.2 UserAccount 核心字段

字段 类型 说明
id BigAutoField 主键
username CharField(30) 登录名同租户唯一不可变更普通员工账号固定为手机号11位数字Tenant Admin 为自定义字符串
password CharField PBKDF2+SHA256 哈希,使用 Django make_password
email EmailField 绑定邮箱,同租户唯一,选填;为空时无法自助找回密码
phone CharField(11) 绑定手机号,加密存储(core.encryption);普通员工必填(同时作为 username 来源Tenant Admin 选填
staff OneToOneField → org.Staff 员工档案绑定普通员工必须Tenant Admin 可为空
is_tenant_admin BooleanField 标记是否为该租户的 Tenant Admin 账号,默认 False
status CharField active / disabled / locked
is_initial_password BooleanField True 时登录成功后强制跳转修改密码页,不可跳过
last_login DateTimeField 最后登录时间
created_at DateTimeField 账号创建时间
created_by ForeignKey → self 创建人(普通员工由 Tenant Admin 创建Tenant Admin 由平台运营创建)

5.5.3 LoginAttempt 登录尝试记录

字段 类型 说明
username CharField 尝试登录的用户名
ip_address GenericIPAddressField 来源 IP
success BooleanField 是否成功
failure_reason CharField wrong_password / wrong_captcha / account_locked
attempted_at DateTimeField 尝试时间

注意LoginAttempt 属于合规审计数据,保留周期建议 ≥ 90 天。

5.5.4 PasswordResetToken

字段 类型 说明
user ForeignKey → UserAccount 关联账号
token CharField(64) 加密 Tokensecrets.token_urlsafe(32)
expires_at DateTimeField 过期时间(创建时间 + 30 分钟)
is_used BooleanField 是否已使用(使用后立即标记为 True
created_at DateTimeField 创建时间

5.6 Electron 客户端登录相关约定

约定项 规格
Tenant ID 存储 electron-storeapp.getPath('userData') + AES 加密,不存储明文
Session Token 存储 内存(global 变量)+ session CookieChromium 管理),不写入磁盘明文文件
登录页加载 客户端主进程根据 Tenant ID 构建目标 URLhttps://{tenant_slug}.fonrey.com/auth/login/),通过 BrowserWindow.loadURL() 加载
多标签页处理 同一 BrowserWindow 内,所有页面共享同一 Session Cookie
客户端登出 调用服务端 POST /api/auth/logout/ 使服务端 Session 失效 + 清除 Chromium Session Cookie
窗口关闭时 Session 保留(不自动登出),下次打开客户端时若 Session 未过期,直接进入系统
强制更新场景 若客户端版本低于服务端 min_required_version,则在登录界面前先展示「请更新客户端」提示,阻断登录流程(参见发布管理模块 PRD

6. 技术注意事项

6.1 依赖与技术选型

依赖项 用途 说明
django.contrib.auth 用户认证基础框架 扩展 AbstractBaseUser 而非直接使用 User 模型,以支持 username 唯一性约束在租户维度而非全局
django-tenants 多租户隔离 UserAccount 属于租户级 SchemaTenant 验证接口属于 shared_apps
Redis 滑块验证 Token 存储、登录失败计数、密码重置 Token 缓存 验证 Keycaptcha_token:{uuid}TTL 3min登录失败 Keylogin_fail:{tenant_id}:{username}
Celery 发送找回邮件 邮件发送异步处理,防止接口响应超时
django-ratelimit 或自定义中间件 接口限流 Tenant 验证接口、登录接口、找回密码接口均需限流
Pillow 滑块拼图图片处理 生成拼图背景图(抠出缺口区域)及对应的拼图碎片图片,输出为 Base64分别通过两个字段返回给前端

6.2 多租户下的 UserAccount 隔离

  • UserAccount 表位于租户 Schema 内django-tenants 租户隔离范围),因此 username 唯一性约束在租户维度生效,不同租户的经纪人可以有相同用户名
  • Tenant 验证接口(/api/auth/tenant/verify/)位于公共 Schemashared_apps),使用 TenantModel 查询
  • 登录、找回密码等接口通过请求域名({tenant_slug}.fonrey.com)切换到对应租户 Schemadjango-tenants 中间件自动处理)

6.3 已知风险

风险 可能性 影响 缓解措施
滑块验证被机器模拟轨迹绕过 服务端同时校验位置偏差 + 轨迹曲线特征(非线性运动特征),拒绝匀速/程序化轨迹;后续可引入设备指纹加固
Tenant ID 枚举攻击(暴力试探) Tenant 验证接口限流每IP每分钟≤10次返回结果不区分「未找到」与「已禁用」
密码重置 Token 泄露 Token 单次有效、30分钟过期、HTTPS 传输
邮件发送失败导致用户无法找回密码 邮件发送失败写入告警日志,管理员可通过后台查看 Token 手动告知用户
多端同时登录同一账号 高(日常场景) 本期允许,后续如需踢出,可在 Token 机制中引入版本号

6.4 开放问题(开发前需确认)

  • 邮件服务商选型:使用 SendGrid / 阿里云邮件推送 / SMTP 自建?需运维确认 — 负责人:后端负责人 — 截止:开发启动前
  • Session 有效期默认值8 小时是否满足各租户需求?是否允许租户管理员自行配置?— 负责人:产品经理 — 截止:开发启动前
  • 滑块拼图实现方案自研Pillow 生成图片 + 前端拖拽组件)还是集成第三方行为验证服务(如极验 GeeTest / 网易易盾)?自研可控但需维护图库;第三方开箱即用但引入外部依赖,需评估数据合规要求 — 负责人:后端负责人 + 安全 — 截止:开发启动前
  • 账号锁定通知:账号被锁定后,是否自动发邮件通知用户和/或管理员?— 负责人:产品经理 — 截止:开发启动前
  • 历史密码校验范围:最近 3 次是否足够?是否需要额外规则(如不能与用户名相同)?— 负责人:产品经理 — 截止:开发启动前

7. 发布计划

阶段 时间 受众 准入门槛
内部 Alpha 待定 研发团队 + 1 家种子租户 核心流程Tenant 识别 + 账密登录 + 找回密码)无 P0 Bug
封闭 Beta 待定 5 ~ 10 家测试租户 登录成功率 ≥ 99%,验证码拦截机制正常运作
正式发布 待定 全量租户 Beta 阶段无未修复的安全漏洞;帮助文档发布

回滚标准:若正式发布后 24 小时内登录失败率(非验证码拦截原因)超过 2%,或出现账号数据泄露事件,立即回滚并启动安全审查。


8. 附录

8.1 登录状态流转图

[未识别 Tenant]
    │ 输入有效 Tenant ID
    ↓
[未登录]
    │ 账密登录成功
    ↓
[初始密码状态](如账号为初始密码)
    │ 强制修改密码成功
    ↓
[已登录 - Active Session]
    │ Session 过期 / 主动登出 / 管理员强制登出
    ↓
[未登录](跳转登录界面)

[账号锁定状态]5次错误后
    │ 30 分钟后自动解锁 或 管理员手动解锁
    ↓
[未登录](可重新登录)

8.2 接口清单汇总

接口 方法 Schema 位置 是否需要鉴权 说明
/api/auth/tenant/verify/ POST Publicshared Tenant ID 验证
/api/auth/captcha/ GET Tenant 获取滑块拼图验证码(返回背景图 Base64 + 碎片图 Base64 + 验证 Token
/api/auth/captcha/verify/ POST Tenant 提交滑动轨迹 + 位置,服务端校验并返回一次性通过凭证(供登录接口使用)
/api/auth/login/ POST Tenant 账号密码登录
/api/auth/logout/ POST Tenant 登出,使 Session 失效
/api/auth/recover/username/ POST Tenant 发起找回用户名
/api/auth/recover/password/request/ POST Tenant 发起找回密码(发送邮件)
/api/auth/recover/password/reset/ POST Tenant Token 鉴权) 提交新密码
/api/auth/login/phone/ POST Tenant 预留,手机验证码登录
/api/auth/wechat/qrcode/ GET Tenant 预留,获取微信二维码
/api/auth/wechat/callback/ POST Tenant 预留,微信扫码回调

8.3 相关文档参考

  • 客户端发布管理模块 PRDProject/fonrey/PRD/发布管理/客户端发布管理模块PRD.md
  • 组织人事管理模块 PRDProject/fonrey/PRD/组织人事管理/组织人事管理模块PRD.md
  • 权限管理模块 PRDProject/fonrey/PRD/权限管理/权限管理模块PRD.md
  • 系统管理模块 PRDProject/fonrey/PRD/系统管理/系统管理模块PRD.md
  • 技术栈文档:Project/fonrey/TECH_STACK/TECH_STACK.md