Files
Lottery/docs/BIZ-127_完整用户系统架构_v1.0.md
architect bf4d100e14 docs(BIZ-127): 完整用户系统架构 v1.0 + SQL DDL
- 5 张表: users / sessions / password_reset_tokens / operation_logs / auth_settings
- 鉴权中间件流程图(日志 → 鉴权 → 业务)
- Cookie 安全策略表(SameSite=Lax, httpOnly, 30 天)
- 多用户扩展:数据层预留, UI/API 不暴露
- 兼容性矩阵:Bearer 双轨 24h 过渡
- 性能 + 安全边界 5 项决策

关联: BIZ-109 → BIZ-127 → BIZ-129
评审: 陆怀瑾 (COO) 已解锁 memory_search 依赖
2026-09-04 20:32:24 +08:00

10 KiB
Raw Permalink Blame History

BIZ-127 完整用户系统架构设计 v1.0

项 内容
版本 v1.0
作者 梁思筑(architect)
日期 2026-09-04
关联卡片 BIZ-127(架构)→ BIZ-129(实施)
父卡片 BIZ-109(P0 修复闭环)
关联 PRD BIZ-111(产品 PRD)
存储位置 docs/BIZ-127_完整用户系统架构_v1.0.md
同步位置 思源知识库 /standards/完整用户系统架构 v1.0

一、设计目标

实现完整的账号密码登录体系,支持长期免登录(30 天),账号密码 + Session Cookie 方案,不在 URL/前端暴露 token。


二、数据模型

完整 SQL DDL 见 docs/auth-schema.sql(9.7KB)。

包含 5 张表:

  1. users —— 用户表(bcrypt 密码哈希、登录失败计数、锁定时间戳、多租户/角色预留)
  2. sessions —— 服务端会话(DB 存,非 JWT;存 session_hash、password_epoch 用于改密失效)
  3. password_reset_tokens —— 密码重置 token(一次性、1 小时过期、限速 3 次/小时)
  4. operation_logs —— 操作日志(90 天保留、多租户索引预留)
  5. auth_settings —— 全局鉴权配置(bcrypt cost、session 寿命、锁定参数)

关键决策

决策点 选择 理由
Session 存储 DB 存 可控失效、改密立即踢人;JWT 不适合内网场景
重置 token 存储 DB 存 一次性、可控、可限速;JWT 自包含不可撤销
密码哈希 bcrypt cost=12 行业标准,登录耗时 ~250ms,安全/性能平衡
多用户扩展 数据层预留 + UI 不暴露 见下文"多用户扩展决策"

三、鉴权中间件流程图

请求进入
   │
   ▼
┌──────────────────────┐
│ 日志中间件           │  ← 记录 method/path/IP/UA
│ (log_request)        │
└──────────┬───────────┘
           │
           ▼
┌──────────────────────┐
│ 鉴权中间件           │  ← 优先读 cookie,fallback Bearer
│ (authenticate)      │     Bearer 仅用于内部服务调用
└──────────┬───────────┘
           │
           │ 判断:path 是否在白名单?
           │
     ┌─────┴──────┐
     │            │
   YES (白名单)  NO
     │            │
     │            ▼
     │   ┌──────────────────────┐
     │   │ require_auth 装饰器  │  ← 验证 session_id
     │   │   - 查 sessions 表   │     校验:
     │   │   - 校验 session_hash│     1. session 存在
     │   │   - 校验 expires_at  │     2. 未撤销
     │   │   - 校验密码 epoch   │     3. 未过期
     │   │   - 续期(如活跃)   │     4. password_epoch 匹配
     │   └──────────┬───────────┘
     │              │
     │              ▼
     │   ┌──────────────────────┐
     │   │ 业务中间件           │
     │   │ (业务逻辑处理)       │
     │   └──────────┬───────────┘
     │              │
     │              ▼
     │   ┌──────────────────────┐
     │   │ 日志中间件(出口)   │  ← 记录 status/duration/user_id
     │   └──────────────────────┘
     │
     └──▶ 业务中间件(白名单接口无需鉴权)

5 个鉴权端点

端点 方法 说明
/api/login POST 账号密码 → 校验 → 创建 session → 设置 cookie
/api/logout POST 清 cookie → 撤销 session(revoked_at=now)
/api/password-reset/request POST 生成 token → 邮件/日志通知(内网暂用日志)
/api/password-reset/confirm POST 校验 token → 改密码 → 撤销该用户所有 session

装饰器改造

# 现状
@require_auth
def api_records(): ...

# 改造后(伪代码,BIZ-129 实施)
@require_auth  # 内部自动读 cookie 或 Bearer
def api_records(): ...

# 新增装饰器
@require_admin  # role == 'admin' 才允许
def admin_only_api(): ...

四、Cookie 安全策略

项 值 决策依据
名称 lotto_session 业务前缀,避免与其它服务冲突
httpOnly true ✅ 阻止 XSS 读取 cookie
secure true(生产) / false(内网 HTTP 调试) 内网暂不强制 HTTPS,但配置项保留
sameSite Lax ✅ 防 CSRF;Strict 会破坏外链跳转登录
path / 全站生效
maxAge 30 * 24 * 3600 秒(30 天) 满足"长期免登录"诉求
domain 当前域(不设) 内网单域,不跨子域

防 CSRF 决策

不单独引入 CSRF token,理由:

  • SameSite=Lax 已防 95% CSRF(POST 表单跨站不携带 cookie)
  • 关键操作(删除、改密码)二次校验(要求输入当前密码或确认)
  • 如未来扩展第三方嵌入或 iframe 调用,再加 CSRF token

Session 续期策略

  • 用户每次活跃请求,若 expires_at - now < 24h,则续期 30 天
  • last_seen_at 用于清理过期 session

五、多用户扩展决策(4 个判断点)

判断点 是否预留 决策
1. 数据库 users.tenant_id / role 字段 ✅ 预留 已建字段,索引就绪,单租户默认 1
2. API 路径 /api/admin/users/* ❌ 不预留 过度设计;未来需要时再加,无需现在占坑
3. 前端「多用户切换」入口 ❌ 不预留 UI 保持单用户登录,多用户场景届时再迭代
4. 操作日志按 tenant_id 分区 ✅ 预留索引 已建 idx_oplog_tenant_time,分区待数据量>10万再分表

预留原则:数据库层 vs API/UI 层

  • 数据层:必须预留(成本低、不影响当前 UI)
  • API/UI 层:不预留(避免过度设计、未来按需添加)

六、兼容性矩阵

项 当前实现 方案 C 后 迁移路径
/api/status 无鉴权 保持无鉴权(status 公开) 无变更
/api/records Bearer 鉴权 Cookie 鉴权 + Bearer 备用 双轨运行 24h,前端切到 cookie 后撤 Bearer
/api/history Bearer 鉴权 Cookie 鉴权 + Bearer 备用 同上
/api/generate Bearer 鉴权 Cookie 鉴权 + Bearer 备用 同上
/api/statistics Bearer 鉴权 Cookie 鉴权 + Bearer 备用 同上
/api/download/* Bearer 鉴权 Cookie 鉴权 + Bearer 备用 同上
Bearer token LOTTO_API_TOKEN env 强制 保留 env(内部脚本用) 不删除,作为内部服务通道
Cookie 无 lotto_session 30 天 新增
默认账号 无 lotto_admin + 临时密码 首次登录强制改密

双轨过渡策略:

  1. 上线 Cookie 鉴权后,前端自动改用 cookie
  2. Bearer 通道保留 24h 给内部脚本迁移
  3. 24h 后仅允许 Bearer 用于 /api/internal/*(如有)
  4. 监控 /api/records 等接口的鉴权失败日志,确认为 0 后撤 Bearer

七、性能 + 安全边界(5 项决策)

项 决策 理由
1. bcrypt cost=12 登录耗时 目标 < 500ms(实测 ~250ms) Ubuntu 22.04 + bcrypt cost=12 单次 ~250ms,远低于 500ms 上限
2. Session 失效机制(改密) 改密码后该用户所有 session 立即失效 session 表 password_epoch 字段;登录时快照,改密后 password_changed_at 更新,session 自动失效
3. 找回密码 token 速率限制 每用户 3 次/小时 防爆破;超限返回 429
4. 登录失败锁定 5 次失败锁定 15 分钟 users.failed_attempts + locked_until;自动解锁
5. 操作日志保留周期 90 天 满足审计需求;月度归档到 operation_logs_archive_yyyyMM;>10万行/天时启动分区

八、验收 Checklist

  • 完整 SQL DDL(含迁移脚本) → docs/auth-schema.sql
  • 鉴权中间件流程图(文字描述) → 第三节
  • Cookie 安全策略完整 → 第四节
  • 多用户扩展接口明确 → 第五节
  • 兼容性矩阵 → 第六节
  • 性能 + 安全边界 → 第七节
  • 架构文档沉淀(思源 + Git) → 同步进行中

九、对下游约束(PRD BIZ-111 + 实施 BIZ-129)

给 BIZ-111 PRD 的约束

  1. 登录页必须有:用户名、密码、"记住我"勾选(默认勾选=30天免登录)
  2. 找回密码流程:输入用户名 → 后台生成 token → 通过日志/邮件通知(内网暂用日志)
  3. 首次登录强制改密:检测 password_changed_at == created_at 时,强制跳改密页
  4. UI 不暴露多用户切换、注册入口(保持单账号)

给 BIZ-129 实施的约束

  1. 必须用 bcrypt cost=12,不要用 sha256/md5/pbkdf2
  2. session_id 必须 32 字节 URL-safe base64(secrets.token_urlsafe(32))
  3. session_hash = sha256(session_id + server_secret),secret 存 env
  4. 不改 LOTTO_API_TOKEN 流程,保留 Bearer 作为内部通道
  5. /api/status 必须保持公开,不能误加鉴权
  6. BIZ-109 严格鉴权基线不能回退(不能因为 Cookie 引入新的 401 路径)

十、风险与待澄清

风险 影响 应对
首次部署 bcrypt 哈希生成失败 登录失败 BIZ-129 部署前在本地预生成哈希嵌入 init SQL
Cookie 30 天过长,安全边界争议 安全 auth_settings.session_max_age_sec 运行时可调
多用户预留字段被误用 数据污染 在 BIZ-129 代码层加 tenant_id == 1 断言
操作日志 90 天后未归档 磁盘膨胀 BIZ-129 实施时同时交付归档脚本

十一、变更历史

版本 日期 变更 作者
v1.0 2026-09-04 初版(BIZ-127 架构交付) 梁思筑