本域总览见《账号计费中心 · 结构大纲》。本文为该域完整设计:定位与边界 / 角色与依赖 / 数据模型 / 核心流程 / 接口契约 / 关键约束与验收标准。
定位:终端用户身份与凭证的统一入口,同时承担平台统一登录(OIDC IdP)的身份提供方职责。回答两个问题——「你是谁」(身份)与「如何证明你是你」(凭证)。
边界
identity_verified / identity_level),不实现核验。登录方式口径
| 角色 | 关系 | 说明 |
|---|---|---|
| 终端用户 | 主体 | 个人账号;企业注册人(注册即成为组织拥有者) |
| 用户门户 | 消费方 | 承载注册/登录/账号设置界面;登录成功后跳单点登录回跳端点 |
| 统一登录客户端(RP) | 消费方 | 经授权码 + PKCE 换取令牌;scope 含门户的客户端走浏览器原生兑换,控制台类客户端必须经集群内 BFF 兑换 |
| 通知与触达域 | 被调 | 发送短信 / 邮件验证码 |
| 运营管理域 | 被调 | 运营人员身份与权限范围(独立主体,不复用终端用户身份) |
| 审计 | 被调 | 注册 / 登录 / 改密 / 换绑等动作落安全日志,门户可查近 90 天 |
| 全部业务域 | 依赖方 | 一切需要登录态的接口以本域签发令牌为唯一身份依据 |
令牌契约(对外提供)
sub / type / roles / exp / iat / jti / typ=portal。typ=portal)用于业务鉴权;过渡令牌(typ=wechat_bind)仅可用于绑定手机流程,业务鉴权一律拒绝。独立表(无外键,自持一次性令牌 / 按来源 IP 与身份串计数,随 TTL 或周期清理收敛):captchas · slider_captchas · login_throttles
本域为最底层身份域,不引用其他域的表,对外只做被引用方;上图为 users 的下游引用分布。
| 表 | 职责 | 关键字段 |
|---|---|---|
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 |
UPDATE ... RETURNING),杜绝并发双消费。verification_tickets 与协议同意表均为一次性 / 只增语义:前者消费即失效,后者永不覆盖(当前生效快照另存于 users)。邮箱验证码校验通过(一次性票据)→ 提交账号名 + 密码(弱密码 422)→ 协议勾选留痕 → 建号。企业注册额外建组织并由注册人成为拥有者。注册成功即调度账户状态键发布(新账号准入语义见账户状态与准入域)。
(scope, subject, window_start) 唯一、单条 UPSERT 原子自增;过期行由周期清理收敛(身份串完全由攻击者可控,无清理可被低成本撑爆)。session_invalid_before(批量失效访问 / 刷新令牌)与 rt_invalid_before(刷新令牌单活水位)。换绑邮箱 / 手机需新值验码 + 旧凭证占有因子二选一(旧联系方式验证码,或登录密码确认)。授权依据是「账号是否已有可自证的找回通道」,而非「该类型有无已验证旧值」——仅当账号不存在任何已验证联系方式时才豁免第二因子;否则会给攻击者开一条免第二因子的找回通道生产线。绑定微信同为新增登录入口,口径与此一致。
scope 含门户的客户端,兑换时另加发门户令牌对。签发 state(防 CSRF,10 分钟)→ 回调换 openid → 已绑定手机直接登录;未绑手机发过渡令牌(15 分钟,仅可调绑定手机端点)→ 绑定手机后换发正式门户令牌。首次登录可即时建号,建号前须完成绑定手机(手机为实名锚点)。
token 2 分钟有效、横向容差 2px、轨迹须非匀速、单 token 最多校验 2 次;校验通过签发一次性票据,发码凭票据一次性消费。token 通过率 < 5%(容差 × 尝试数 / 取值域)。target 重发间隔 60 秒、每日 10 次;单 IP 每日 50 次;素材生成单 IP 每日 200 次。token 失败上限 2 次、单 IP 窗口内 15 次 → 429。协议版本由服务端单一常量定义;同意写入只增历史表 + 审计,并在用户行留「当前生效」快照。版本不一致(未同意 / 协议已更新)时,下单被 403 terms_not_agreed 拦下——门禁开关可按环境关闭(灰度 / 演示)。
/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 |
当前登录用户(门户鉴权) |
/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 |
注销账号 |
/api/account/slider-captcha)| 方法 | 路径 | 语义 |
|---|---|---|
| GET | /generate |
生成滑块素材(随机背景 + 缺口);返回 token + 图片 + secret_key |
| POST | /verify |
校验拖动(坐标容差内 + 轨迹非匀速)→ 一次性票据;失败累计达上限即作废该 token |
/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;需过第二因子 |
/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 |
统一登出落地页 |
UPDATE ... RETURNING 抢占,并发双消费恒至多一次成功。| 项 | 验收口径 |
|---|---|
| 注册闭环 | 邮箱验证码注册成功;弱密码 422;未勾选协议不可注册;企业注册后注册人角色为拥有者 |
| 登录四通道 | 密码(联系方式 / 账号名)、验证码、一键注册登录、微信扫码各自可登录并签发门户令牌 |
| 门禁分级 | IP 达 10 次失败→要求图形码,达 30 次→429 且带 Retry-After;身份达 2 次→要求图形码,达 5 次→423 且 15 分钟后自动解锁 |
| 防枚举 | 存在 / 不存在账号在门禁各档位返回完全相同的状态码与文案;响应耗时无可利用差异 |
| 令牌单活 | 同一刷新令牌连续两次使用,第二次 401;多端登录仅最后轮换者存活 |
| 改密自救 | 改密后旧访问 / 刷新令牌全部 401,且原 IdP 会话不可再换出客户端令牌 |
| 换绑门禁 | 已有任一已验证联系方式时,仅凭新值验证码换绑必须被拒(要求第二因子) |
| 单点登录 | 授权码 5 分钟内单次消费;重放 / PKCE 不匹配 / 回调地址不符一律拒绝;登出链逐客户端生效且幂等 |
| 人机验证 | 滑块坐标超容差或匀速轨迹必失败;单 token 超尝试上限即作废;发码须凭未消费票据 |
| 频控 | 同 target 60 秒内重发被拒;超日限发码被拒;单 IP 超素材生成日限被拒 |
| 协议门禁 | 未同意当前版本时下单 403 terms_not_agreed;同意后放行;同意历史可查且不可改写 |
| 注销 | 注销后该身份全部令牌失效、不可再登录,关联凭证按级联清理 |