D01 账号与身份域 · 设计文档

本域总览见《账号计费中心 · 结构大纲》。本文为该域完整设计:定位与边界 / 角色与依赖 / 数据模型 / 核心流程 / 接口契约 / 关键约束与验收标准。


1. 定位与边界

定位:终端用户身份与凭证的统一入口,同时承担平台统一登录(OIDC IdP)的身份提供方职责。回答两个问题——「你是谁」(身份)与「如何证明你是你」(凭证)。

边界

登录方式口径


2. 角色与依赖

角色 关系 说明
终端用户 主体 个人账号;企业注册人(注册即成为组织拥有者)
用户门户 消费方 承载注册/登录/账号设置界面;登录成功后跳单点登录回跳端点
统一登录客户端(RP) 消费方 经授权码 + PKCE 换取令牌;scope 含门户的客户端走浏览器原生兑换,控制台类客户端必须经集群内 BFF 兑换
通知与触达域 被调 发送短信 / 邮件验证码
运营管理域 被调 运营人员身份与权限范围(独立主体,不复用终端用户身份)
审计 被调 注册 / 登录 / 改密 / 换绑等动作落安全日志,门户可查近 90 天
全部业务域 依赖方 一切需要登录态的接口以本域签发令牌为唯一身份依据

令牌契约(对外提供)


3. 数据模型

3.1 域内关联总览

持有凭证协议同意留痕全局会话授权码本人安全审计签发关联会话验码签发

users

uuid

id

PK

string

source

string

hashed_password

boolean

is_active

string

agreed_terms_version

timestamp

session_invalid_before

timestamp

rt_invalid_before

user_credentials

uuid

id

PK

uuid

user_id

FK

string

credential_type

string

credential_value

timestamp

verified_at

boolean

is_primary

terms_acceptances

uuid

id

PK

uuid

user_id

FK

string

version

string

source

timestamp

created_at

oauth_sessions

uuid

session_id

PK

uuid

user_id

FK

timestamp

expires_at

timestamp

revoked_at

authorization_codes

uuid

code

PK

string

client_id

FK

uuid

oauth_session_id

FK

uuid

user_id

FK

string

code_challenge

timestamp

consumed_at

operation_logs

uuid

id

PK

uuid

actor_id

FK

string

action

string

target_type

string

status

timestamp

created_at

oauth_clients

uuid

id

PK

string

client_id

jsonb

redirect_uris

string

scope

boolean

is_active

verification_codes

uuid

id

PK

string

channel

string

target

string

purpose

string

code_hash

integer

attempt_count

timestamp

consumed_at

verification_tickets

uuid

id

PK

uuid

code_id

FK

string

purpose

string

target

timestamp

consumed_at

图 1

独立表(无外键,自持一次性令牌 / 按来源 IP 与身份串计数,随 TTL 或周期清理收敛):captchas · slider_captchas · login_throttles

3.2 域间引用

users
被引用枢纽

租户与组织域
organizations · memberships
invitations · groups · group_members

订购与支付域
account_api_keys · pay_channel_configs

钱包与账务域
customer_grants · invoice_applications

商品与价目
products · product_prices · price_rules

风控与合规域
user_real_name_verifications
org_real_name_verifications

通知与触达域
notifications · email_configs · sms_configs
email_templates · sms_template_bindings

运营管理域
platform_staff · platform_settings

图 2

本域为最底层身份域,不引用其他域的表,对外只做被引用方;上图为 users 的下游引用分布。

3.3 表清单(12 表)

表 职责 关键字段
users 登录身份主体(一人一行) source(注册来源)、display_name、hashed_password(可空——外部身份账号无密码)、is_active、登录安全(failed_login_count / locked_until / last_login_at / last_login_ip / last_login_user_agent)、会话水位(session_invalid_before / rt_invalid_before)、协议留痕(agreed_terms_version / agreed_terms_at)、实名回写位(identity_verified / verification_method / verified_at / identity_level)
user_credentials 联系方式与外部身份统一凭证 credential_type(phone / email / account / 微信 openid / unionid)、credential_value、verified_at(NULL = 未验证)、is_primary;约束:已验证凭证全局唯一、每用户每类型联系方式至多一行、账号名同人唯一、外部身份凭证建行即已验证
terms_acceptances 协议同意历史(append-only) user_id、version、source(注册 / 重新确认 / 微信首登)、ip、user_agent、created_at
verification_codes 短信 / 邮箱一次性验证码 channel、target、purpose(注册 / 登录 / 换绑 / 重置密码)、code_hash(哈希存储)、expires_at、consumed_at、attempt_count、ip
verification_tickets 验证码校验通过后的一次性凭证 code_id、purpose、target、expires_at、consumed_at;purpose + target 绑定防跨场景重放
captchas 图形验证码 answer、expires_at、consumed_at、attempt_count、ip
slider_captchas 滑块验证 token、answer_x / answer_y(仅服务端)、secret_key、expires_at、verified_at、failed_attempts、ticket、consumed_at、ip
login_throttles 登录失败限流计数器 scope(ip / identity)、subject(IP 或归一化身份串)、window_start、failure_count;(scope, subject, window_start) 唯一
oauth_clients 统一登录客户端注册 client_id、client_name、redirect_uris(精确匹配白名单)、frontchannel_logout_uri、scope(含 portal 与否决定兑换形态)、is_active
oauth_sessions 身份提供方全局会话 session_id、user_id、expires_at(服务端硬上限 7 天,固定不滚动)、revoked_at
authorization_codes 一次性授权码 code、client_id、redirect_uri、code_challenge + code_challenge_method(PKCE)、oauth_session_id、user_id、nonce、expires_at、consumed_at
operation_logs 本人视角安全审计(与业务变更同事务写入) actor_id、action、target_type、target_id、status、detail、operator_ip、operator_user_agent、created_at

3.4 设计约束


4. 核心流程

4.1 注册

未通过通过,签发一次性票据校验失败票据一次性消费通过弱密码通过是否

填写邮箱

滑块门禁前置校验

拒绝发码

下发邮箱验证码

提交验证码

错误计数,达限 429

提交账号名 + 密码

422 拒绝

协议勾选留痕

建号与凭证落库

企业注册

建组织,注册人成为拥有者

调度账户状态发布

图 3

邮箱验证码校验通过(一次性票据)→ 提交账号名 + 密码(弱密码 422)→ 协议勾选留痕 → 建号。企业注册额外建组织并由注册人成为拥有者。注册成功即调度账户状态键发布(新账号准入语义见账户状态与准入域)。

4.2 登录(含门禁)

是否是否是否通过命中未命中成功失败

提交身份串

IP 维度窗口内失败 ≥ 30

429 限流,Retry-After 指向窗口末

身份维度窗口内失败 ≥ 5

423 锁定 15 分钟

IP ≥ 10 或 身份 ≥ 2

要求图形验证码

反查凭证

凭证命中

校验密码

恒定时间假哈希路径

签发令牌对,更新登录安全字段,写安全日志

双维度计数 +1

响应码与文案同存在账号完全一致

图 4

4.3 会话与令牌生命周期

统一登录服务账号服务客户端统一登录服务账号服务客户端alt[抢占成功][重放 / 他端已消费 / 水位已前移]修改密码 / 重置密码 / 换绑联系方式 / 注销 / 首次设置密码同时前移门户水位并撤销该用户全部未撤销统一登录会话呈交刷新令牌条件更新原子抢占水位(仅放行水位不晚于所呈令牌签发时间)访问令牌 30 分钟 + 新刷新令牌 7 天401
图 5

4.4 换绑与第二因子

无(完全豁免)有二者其一通过均未通过

发起换绑联系方式 或 绑定微信

新值验码

账号是否已有已验证可自证找回通道

直接完成换绑

旧联系方式验证码 或 登录密码确认

拒绝

前移令牌水位,生效新凭证

图 6

换绑邮箱 / 手机需新值验码 + 旧凭证占有因子二选一(旧联系方式验证码,或登录密码确认)。授权依据是「账号是否已有可自证的找回通道」,而非「该类型有无已验证旧值」——仅当账号不存在任何已验证联系方式时才豁免第二因子;否则会给攻击者开一条免第二因子的找回通道生产线。绑定微信同为新增登录入口,口径与此一致。

4.5 单点登录(授权码 + PKCE)

账号服务统一登录服务业务门户浏览器账号服务统一登录服务业务门户浏览器alt[校验失败][校验通过且已有统一登录会话][校验通过但无会话]全局会话服务端硬上限 7 天,固定不滚动,与业务刷新令牌互不吊销统一登出为续跳链,逐客户端通知(best-effort),链尽回落地页访问受保护页面跳转授权端点GET /oauth/authorize参数白名单校验400 HTML,绝不重定向到未验证地址回跳门户并附授权码(5 分钟 / 单次消费 / PKCE S256)跳统一登录页,暂存回跳目标 10 分钟完成登录回跳门户并附授权码集群内兑换授权码校验未消费 / 未过期 / 客户端与回调一致 / PKCE身份令牌(scope 含门户者另加发门户令牌对)
图 7

4.6 微信扫码

账号服务门户前端用户账号服务门户前端用户alt[已绑定手机][未绑定手机]首次登录可即时建号,建号前须完成绑定手机(手机为实名锚点)打开扫码登录索取 statestate 10 分钟,防 CSRF扫码确认,回调带回 openid反查绑定关系换发正式门户令牌过渡令牌 15 分钟,仅可调绑定手机端点提交手机并验码完成绑定换发正式门户令牌
图 8

签发 state(防 CSRF,10 分钟)→ 回调换 openid → 已绑定手机直接登录;未绑手机发过渡令牌(15 分钟,仅可调绑定手机端点)→ 绑定手机后换发正式门户令牌。首次登录可即时建号,建号前须完成绑定手机(手机为实名锚点)。

4.7 人机验证门禁

验证服务客户端验证服务客户端alt[校验通过][未通过]登录失败重试场景改用图形验证码:5 分钟有效,单条最多校验 3 次,达限作废发码频控:同目标间隔 60 秒 / 每日 10 次;单 IP 每日 50 次申请滑块素材素材 + token(2 分钟,容差 2px,轨迹须非匀速)提交滑动轨迹(同一 token 最多校验 2 次)一次性发码票据凭票据请求发码(票据一次性消费)下发验证码拒绝,单 IP 窗口 15 次即 429
图 9

4.8 协议同意与门禁

是否 / 协议已更新

服务端单一协议版本常量

已同意当前版本

放行下单

403 terms_not_agreed 拦下

重新同意:写只增历史表 + 审计,并更新用户行当前快照

图 10

协议版本由服务端单一常量定义;同意写入只增历史表 + 审计,并在用户行留「当前生效」快照。版本不一致(未同意 / 协议已更新)时,下单被 403 terms_not_agreed 拦下——门禁开关可按环境关闭(灰度 / 演示)。


5. 接口契约

5.1 门户认证(/api/account/auth)

方法 路径 语义
GET /terms/{kind} 协议文本(用户协议 / 隐私政策 / 服务等级协议),公开可读(注册页未登录即需展示)
GET /captcha 签发图形验证码;联调开关开启时附答案(生产禁用)
GET /login-options 登录页可用登录方式(公开):手机通道可用性与微信登录可用性各自独立判定,任一开关来源不可用时该入口不下发
POST /verification-code/send 发码:落库 + 渠道发送;返回真实发送结果(渠道未配置时不谎报成功);联调开关开启时附明码
POST /verification-code/verify 校验验证码 → 签发一次性票据(供注册 / 换绑等后续提交)
POST /login 密码登录(任一已验证联系方式或账号名);成功签发门户令牌对
POST /login-or-register 验证码登录:已注册 → 等价验证码登录;未注册 → 404 contact_not_registered(验证码已证明联系方式所有权,无枚举风险),前端引导跳独立注册页
POST /reset-password 忘记密码:凭一次性票据(purpose=reset_password)反查用户;target 取自票据不信任前端;重置后旧令牌全部失效
POST /refresh 刷新令牌(单活轮换):消费即失效,重放 401
POST /sso/exchange 单点登录授权码兑换(浏览器原生形态):仅服务注册 scope 含 portal 的客户端;PKCE S256 校验 + 授权码单次消费
GET /me 当前登录用户(门户鉴权)

5.2 注册与账号管理(/api/account)

方法 路径 语义
POST /register 个人注册(账号名 + 密码 + 协议门禁)
POST /register/organization 企业账户注册(注册人自动成为组织拥有者)
POST /register/invite 邀请注册
POST /me/accept-terms 同意当前协议版本:写同意历史 + 审计;版本以服务端当前值为准(不接受客户端传旧版本)
GET / PATCH /profile 读取 / 更新资料(昵称即改即存;头像仅接受预置头像路径)
GET /preset-avatars 预置头像列表
POST /password 修改密码(需旧密码)
POST /password/set 首次设置密码(仅无密码账号;凭验证码,target 服务端解析)
POST /username/set 首次设置账号名(一期不可改)
POST /contact/change 换绑 / 首次绑定邮箱或手机(新值验码 + 旧凭证第二因子)
POST /contact/verify 重新验证已绑定的联系方式
GET /security-logs 安全日志(近 90 天登录 / 改密 / 换绑 / 注册 / 组织动作)
DELETE /account 注销账号

5.3 人机验证(/api/account/slider-captcha)

方法 路径 语义
GET /generate 生成滑块素材(随机背景 + 缺口);返回 token + 图片 + secret_key
POST /verify 校验拖动(坐标容差内 + 轨迹非匀速)→ 一次性票据;失败累计达上限即作废该 token

5.4 微信扫码(/api/account)

方法 路径 语义
GET /auth/wechat/qrcode 签发扫码参数(含防 CSRF 的 state);联调模式返回内置授权页二维码,真实模式返回授权跳转地址
GET /wechat/qrcode-status PC 端轮询扫码状态(联调模式):待扫 / 已扫 / 已确认 / 已过期
GET /wechat/mock-auth 联调授权页(仅联调模式)
POST /wechat/mock-auth/scan 联调:置为已扫
POST /wechat/mock-auth/confirm 联调:确认 / 取消
POST /auth/wechat/callback 授权回调:已绑定 → 直接登录;未绑手机(含首次登录建号)→ 发过渡令牌
POST /auth/wechat/bind-phone 绑定手机(短信验证码)后换发正式门户令牌
POST /wechat/bind 已登录账号绑定微信;该微信已被占用 → 409;需过第二因子

5.5 统一登录(IdP,非 /api/account 前缀)

方法 路径 语义
GET /oauth/authorize 授权端点:客户端 / 回调地址 / PKCE 白名单校验 → 会话判定 → 发码或跳登录页
POST /oauth/token 令牌端点:JSON 请求,仅集群内可达(不进公网入口);错误统一标准 OAuth 错误体
GET /.well-known/jwks.json 公钥集合;客户端缓存,遇未知 kid 强制刷新一次
GET /oauth/logout 统一登出(幂等):清 cookie + 续跳链
GET /oauth/logout/continue 登出续跳中转:验签 → 弹出已处理端点 → 重签跳下一个
GET / POST /login IdP 登录页(服务端渲染最小实现):账密 + 回跳恢复;已登录直接续跳
GET /oauth/returnto 单点登录回跳恢复:门户登录成功后整页跳本端点,服务端签发并验签的目标地址,无开放跳转面
GET /loggedout 统一登出落地页

6. 关键约束与验收标准

6.1 约束

  1. 不得泄露账号存在性:门禁判定先于身份反查,且不存在账号与存在账号走完全相同的计数与响应路径;不存在用户路径跑恒定时间假哈希,防时序侧信道。
  2. 凭证不可逆与最小化:密码、验证码答案哈希存储;证书 / 密钥类凭据经环境注入,不入库、不回显。
  3. 状态多副本共享 + 原子消费:图形码 / 滑块 / 验证码状态落库;消费一律 UPDATE ... RETURNING 抢占,并发双消费恒至多一次成功。
  4. 令牌单活:刷新令牌消费即失效、重放 401;改密 / 换绑 / 注销须同时前移门户水位并撤销 IdP 会话,二者缺一即构成自救失败。
  5. 开放跳转面为零:授权端点参数错误一律 400 HTML,不 302;回跳目标由服务端签发并验签,不接受客户端传入。
  6. 令牌端点不可公网直达:令牌兑换端点仅集群内可达,浏览器全程不直连。
  7. 登录页无注入面:服务端渲染登录页不回显任何请求输入,错误统一静态文案。
  8. 能力开关 fail-closed:手机通道、微信登录、单点登录任一能力未就位时其入口不下发;联调模式凭据在生产一律拒绝(白名单判定为「环境 = 开发」,非「≠ 生产」)。
  9. 限流覆盖不存在账号:两维度计数均覆盖「账号不存在」的尝试,否则攻击者可对任意账号试 2 次绕过图形码与锁定。

6.2 验收标准

项 验收口径
注册闭环 邮箱验证码注册成功;弱密码 422;未勾选协议不可注册;企业注册后注册人角色为拥有者
登录四通道 密码(联系方式 / 账号名)、验证码、一键注册登录、微信扫码各自可登录并签发门户令牌
门禁分级 IP 达 10 次失败→要求图形码,达 30 次→429 且带 Retry-After;身份达 2 次→要求图形码,达 5 次→423 且 15 分钟后自动解锁
防枚举 存在 / 不存在账号在门禁各档位返回完全相同的状态码与文案;响应耗时无可利用差异
令牌单活 同一刷新令牌连续两次使用,第二次 401;多端登录仅最后轮换者存活
改密自救 改密后旧访问 / 刷新令牌全部 401,且原 IdP 会话不可再换出客户端令牌
换绑门禁 已有任一已验证联系方式时,仅凭新值验证码换绑必须被拒(要求第二因子)
单点登录 授权码 5 分钟内单次消费;重放 / PKCE 不匹配 / 回调地址不符一律拒绝;登出链逐客户端生效且幂等
人机验证 滑块坐标超容差或匀速轨迹必失败;单 token 超尝试上限即作废;发码须凭未消费票据
频控 同 target 60 秒内重发被拒;超日限发码被拒;单 IP 超素材生成日限被拒
协议门禁 未同意当前版本时下单 403 terms_not_agreed;同意后放行;同意历史可查且不可改写
注销 注销后该身份全部令牌失效、不可再登录,关联凭证按级联清理

6.3 待后续设计细化的开放项