时序图栏目 ·

登录时序图怎么画:验证码/失败重试/风控校验分支怎么补齐

从研发/测试/架构视角讲清登录时序图的画法:先画主成功链路,再用 alt/loop/opt 补齐验证码、失败重试、风控挑战、锁定与降级等分支,并给出可交付的检查清单与常见反例修正。

如果你正在画“登录时序图”,最容易踩的坑不是箭头画法,而是分支不全:验证码要不要画?失败重试怎么表达才不歧义?风控(人机/设备指纹/黑名单/二次校验)插进来后,主链路怎么保持清晰?

这篇文章给你一个可落地的方法:

  • 先画一条“可验收”的主成功链路(能对照接口与日志逐步验证)
  • 再用 alt/loop/opt 把“必须补齐的分支”补上(验证码、失败重试、风控挑战、锁定/冷却、短信/邮箱发送失败等)
  • 最后用一张检查清单保证你交付的时序图既能让研发实现,也能让测试写用例、让架构评审看风险

下面默认你画的是 UML 序列图(时序图),读者主要是产品/研发/测试/架构/写文档的人;学生作业也能直接套用。

1. 先把“登录”说清楚:你到底画的是哪一种?

“登录”至少有 4 种常见形态,你不先定范围,时序图必然越画越乱

  1. 账号+密码(可能带图形验证码)
  2. 短信/邮箱验证码(一次性验证码 OTP)
  3. 第三方 OAuth(微信/企业 SSO/LDAP/SAML)
  4. 二次认证(MFA:短信/邮箱/Authenticator/硬件 Key)

建议你在图的左上角用一句话写范围(相当于图的“契约”):

  • “本图描述:移动端手机号+短信验证码登录,包含失败重试与风控挑战;不包含注册与找回密码。”

这样评审时就不会有人问“那忘记密码呢?”然后强行把你图拖成一坨。

2. 登录时序图为什么总画不完整:因为大家把它当成流程图

很多人画出来的是“用户点按钮→后端返回 token”,看起来对,但不专业。原因是把时序图当成了“步骤列表”。

时序图真正擅长表达的是:

  • 对象之间如何交互(谁调用谁)
  • 同步/异步边界(哪里必须等,哪里可以异步)
  • 失败如何处理(重试、回退、补偿、锁定)
  • 安全控制点(验证码、风控挑战、签名/nonce、会话与 token 的生命周期)

所以画登录时序图的核心不是“把步骤写全”,而是:把交互点与控制点画全

3. 开始画之前,先问 7 个问题(决定你的参与者与分支)

在你动手拉生命线之前,先把下面 7 个问题回答出来,哪怕只写在草稿里:

  1. 客户端是谁?Web / App / 小程序 / H5?是否有 BFF(Backend For Frontend)?
  2. 认证中心是谁?是独立 Auth Service,还是在 User Service 里?
  3. 凭证是什么?密码?短信码?OAuth code?
  4. token 体系是什么?Session Cookie?JWT?Access/Refresh?
  5. 风控在哪里?在网关?在 Auth Service?是外部风控平台?
  6. 失败策略是什么?密码输错次数?短信码错误次数?冷却时间?锁定策略?
  7. 观测点在哪里?登录失败原因码、风控命中、验证码触发、发送短信失败、延迟等指标是否要打点?

你会发现:这些问题一旦确定,参与者(生命线)就“自然长出来了”

4. 生命线怎么拆:一张能交付的登录时序图,至少有这些角色

建议从“职责边界”拆生命线,而不是从“代码模块”硬拆。

最常见、也最清晰的一组是:

  • User:用户(触发交互)
  • Client(Web/App):客户端 UI + 本地存储
  • API Gateway / BFF:统一入口、限流、基础校验
  • Auth Service:认证服务(校验凭证、发 token)
  • User Service / DB:用户数据、密码 hash、账号状态
  • Captcha Service(可选):图形验证码/滑块
  • SMS/Email Provider(可选):短信/邮件通道
  • Risk Engine(风控)(可选但很常见):设备指纹、黑名单、挑战策略
  • Token Store / Redis(视实现):刷新 token、会话、黑名单、验证码计数
  • Log/Metric(可选):审计与监控

你不需要每张图都把这些全画出来,但你至少要解释清楚“没画出来的东西放在哪个边界内”

5. 先画“主成功链路”:不带分支、不带重试,只画一条能跑通的直线

以“手机号+短信验证码登录”为例,主成功链路可以这样简化:

  1. 用户输入手机号,点“获取验证码”
  2. Client 调用 Gateway:发送验证码请求
  3. Gateway → Auth:请求下发 OTP
  4. Auth → Risk:评估是否允许发送(频率、设备、黑名单)
  5. Auth → SMS:发送短信
  6. 用户输入短信码,点“登录”
  7. Client → Gateway:提交手机号+短信码
  8. Gateway → Auth:校验 OTP
  9. Auth → User:读取用户状态(是否禁用/锁定)
  10. Auth:签发 access/refresh token(或 session)
  11. Gateway → Client:返回 token
  12. Client:保存 token,进入已登录态

你会注意到:这条链路里,风控并不是“一个 if”,而是一个明确的交互对象(Risk Engine)。把它画成生命线,后面补分支会容易很多。

消息命名建议(让测试/研发一眼能对上接口)

  • 用“动作 + 关键字段”的风格:
    • requestOtp(phone, scene, deviceId)
    • riskEvaluate(scene, ip, deviceFingerprint, phone)
    • sendSms(templateId, phone, code)
    • verifyOtp(phone, code)
    • issueToken(userId, scopes, clientId)

消息命名越贴近真实接口,你的时序图就越“可实现/可测试”,而不是 PPT。

6. 必补分支 ①:验证码相关分支(什么时候触发、失败怎么返回)

验证码分两类,画法不一样:

  • 图形验证码/滑块:通常是“提高提交门槛”,减少撞库与恶意请求
  • 短信/邮箱 OTP:通常是“凭证本身”(登录就是靠它)

6.1 图形验证码(Captcha)怎么画:通常是风控触发的挑战(Challenge)

最常见的触发逻辑:当 Risk Engine 判定风险升高时,要求先通过 Captcha,再允许发送短信或允许登录。

在时序图里建议这样表达:

  • alt [risk=high] 分支
  • “高风险分支”里插入 Client ↔ Captcha Service 的挑战交互

你可以把它想成:Risk Engine 不直接跟用户交互,而是返回“需要挑战”的决策,挑战由 Captcha Service 承担。

6.2 短信 OTP 的关键分支:发送失败、频率限制、验证码过期

短信 OTP 的分支不补齐,你的图就不适合评审。

至少补这几个 alt

  • alt [rate_limited]:同手机号/同设备/同 IP 触发频控,返回冷却时间
  • alt [sms_provider_failed]:三方通道失败(超时/限流/余额不足),如何降级?是否切换通道?
  • alt [otp_expired]:验证码过期
  • alt [otp_wrong]:验证码错误(是否计数?多少次锁?)

这些分支最终会影响:错误码、提示文案、重试策略、客服排障路径。

7. 必补分支 ②:失败重试怎么画(loop)——“重试”不是一句话

“失败重试”在登录里有三层含义:

  1. 用户重试:用户反复输入密码/验证码
  2. 客户端重试:网络抖动导致的重发请求
  3. 服务端重试:调用下游(短信、风控、DB)的重试与熔断

在时序图里,建议把“用户重试”与“系统重试”分开表达,否则会出现歧义。

7.1 用户重试:用 loop 表达“最多 N 次”

示例(概念表达即可,不必拘泥语法):

  • loop [最多 5 次 / 10 分钟窗口]
    • Client → Gateway:submitLogin(phone, otp)
    • Gateway → Auth:verifyOtp(...)
    • alt [otp_wrong] → 返回错误码 + 剩余次数

关键点:把“窗口期”写出来,否则测试无法写出等价用例。

7.2 客户端重试:必须考虑幂等与重复提交

移动网络下,客户端可能因为超时“以为没提交成功”,于是重发。

你至少要在时序图里体现一个控制点:

  • Client 每次提交带 requestId(或 nonce
  • Auth/Token Store 做去重:dedupe(requestId)

否则你会在评审时被问:重复提交会不会发两次短信?会不会签发多个 token?会不会触发错误计数?

7.3 服务端重试:通常用 opt 标注“可能重试/降级”,并注明超时边界

例如短信通道:

  • opt [sms_timeout]:Auth 切换到备用通道或重试一次
  • 同时在消息旁标注超时:timeout=2smaxAttempts=2

你不必把每一次重试都展开成 10 条箭头,但要把策略与边界写清楚。

8. 必补分支 ③:风控校验怎么插入才清晰(不要把风控画成“一个判断框”)

风控常见的两类介入点:

  • 发送前风控:是否允许发送 OTP(避免短信轰炸)
  • 登录前风控:是否允许签发 token(避免撞库成功后直接拿到会话)

推荐画法:

  1. 把 Risk Engine 画成独立生命线
  2. 在两处调用:riskEvaluate(sendOtp)riskEvaluate(login)
  3. Risk Engine 返回“决策对象”而不是布尔值,例如:
    • ALLOW
    • DENY(reason)
    • CHALLENGE(type=captcha|mfa|kba, params...)

8.1 用 alt 表达三态决策(允许/拒绝/挑战)

  • alt [ALLOW] → 继续主链路
  • alt [DENY] → 返回错误码 + 冷却/封禁信息(必要时隐藏细节)
  • alt [CHALLENGE] → 插入挑战流程(Captcha/MFA)成功后再回到主链路

这比“画一个菱形写风控”专业得多,因为它把系统行为说清楚了。

8.2 风控命中后的“反枚举”提示:别把规则信息泄露给攻击者

你在图里可以加一条注释(Note),提醒产品/前端/测试:

  • 对外提示尽量模糊(例如“操作频繁,请稍后再试”)
  • 详细原因只进日志/审计(供客服/安全排障)

这类注释很适合写在时序图里,因为它是“跨角色共识”。

9. 账户状态分支:锁定、禁用、未注册、需要补全资料

登录经常牵扯“账号状态”,这些状态如果不画出来,测试用例会漏。

建议在 Auth → User Service 取数后,用 alt 补齐至少 4 个状态:

  • alt [user_not_found]:手机号未注册(是否允许自动注册?是否提示?)
  • alt [disabled]:账号被禁用
  • alt [locked]:输错次数过多锁定(返回剩余锁定时间)
  • alt [need_profile]:允许登录但要求补全资料(这会影响 token 的 scope 或跳转)

如果你们有“灰度/AB/合规弹窗”等,也可以放在 opt 里。

10. token / 会话怎么画:画清楚“发了什么”“存了什么”“怎么撤销”

很多登录时序图只画“返回 token”,但评审真正关心的是:

  • token 是 JWT 还是 session id?
  • 是否有 refresh token?存在哪?
  • 登出/封禁怎么撤销?

10.1 推荐把 token 签发拆成两个动作

  • issueAccessToken(userId, exp=15m)
  • issueRefreshToken(userId, exp=30d)(可选)

并画出 refresh token 的落地:

  • Auth → Redis/DB:storeRefreshToken(jti, userId, deviceId)

这样测试与安全评审才知道:撤销是不是可控(例如踢下线)。

10.2 多端登录/单点登录(SSO)怎么表达

如果你们要求“同账号只能一台设备在线”,就需要在时序图里体现:

  • Auth → Token Store:invalidateOldSessions(userId)

如果允许多端,建议在 token 存储里带上 deviceId,并在图上注明“按 deviceId 管理”。

11. 一个可直接照着画的“登录时序图骨架”(文字版)

下面给你一个“骨架”,你可以把它当成画图时的脚手架:

参与者(从左到右):User、Client、Gateway/BFF、Auth Service、Risk Engine、Captcha Service(可选)、SMS Provider(可选)、User Service、Token Store、Log/Metric

主链路(成功)

  1. User → Client:输入手机号
  2. Client → Gateway:requestOtp(phone, deviceId)
  3. Gateway → Auth:requestOtp(…)
  4. Auth → Risk:riskEvaluate(scene=sendOtp, …)
  5. alt [CHALLENGE]
    • Auth → Gateway:needChallenge(type=captcha)
    • Gateway → Client:showChallenge
    • Client ↔ Captcha:completeChallenge
    • Client → Gateway:submitChallengeToken
    • Gateway → Auth:verifyChallengeToken
  6. alt [ALLOW]
    • Auth → SMS:sendSms(…)
    • SMS → Auth:sendResult(ok)
    • Auth → Log:logOtpSent
  7. User → Client:输入验证码
  8. Client → Gateway:submitLogin(phone, otp, requestId)
  9. Gateway → Auth:verifyOtp(…, requestId)
  10. Auth → Risk:riskEvaluate(scene=login, …)
  11. alt [DENY] → 返回失败(模糊提示 + 记录原因)
  12. alt [ALLOW]
    • Auth → User:loadUser(phone)
    • alt [locked/disabled/not_found] → 返回失败
    • Auth → TokenStore:storeSession/refreshToken
    • Auth → Gateway:issueToken(access, refresh)
  13. Gateway → Client:loginSuccess
  14. Client:保存 token,进入已登录态

失败重试(loop)

  • loop [最多 N 次 / 窗口 T] 包裹第 8~12 步

你照着这个骨架画,哪怕你们系统细节不同,也不会缺“关键控制点”。

12. 常见反例(看起来对但不专业)与修正

下面这些是登录时序图里最常见的“评审扣分项”,我把“为什么不对”和“怎么改”也写出来,方便你直接对照修图。

反例 1:把风控画成一个注释/菱形

问题:看不出风控调用发生在什么时候、返回什么、失败怎么处理。

修正:把 Risk Engine 作为生命线;返回三态决策(ALLOW/DENY/CHALLENGE);用 alt 展开三条路径。

反例 2:只画成功路径,不画错误码与锁定

问题:测试无法覆盖;安全风险点没暴露;上线后客服无法排障。

修正:补 alt [otp_wrong]alt [otp_expired]alt [locked],并注明“计数策略 + 窗口期”。

反例 3:重试写成“失败重试”四个字

问题:看不出是用户重试还是系统重试;也看不出上限与幂等。

修正:用户重试用 loop [N 次/窗口];客户端重试强调 requestId 去重;服务端重试用 opt 标注次数与超时。

反例 4:token 只写“返回 token”,没写存储与撤销

问题:单点/踢下线/风控拦截后撤销不清楚;安全评审会追问。

修正:画出 Token Store(Redis/DB)落地;注明 access/refresh 的 TTL;标注撤销路径(invalidate)。

反例 5:把“发送短信”画在 Client 直接调用 SMS

问题:现实里短信通道通常在服务端;Client 直连会暴露密钥与规则。

修正:Client 只调用 Gateway/Auth;Auth 统一调用 SMS Provider,并做频控/风控。

13. 交付前检查清单(给研发/测试/文档都能用)

你可以在 PR/评审前拿这份清单自检:

  • 图左上角写清楚范围:登录类型、是否包含注册/找回、是否包含 MFA
  • 参与者按职责边界拆分:Client / Gateway / Auth / Risk / User / TokenStore(至少这些)
  • 主成功链路能对上真实接口与日志字段(消息命名不抽象)
  • 至少补齐 3 类必补分支:验证码(触发/失败)、失败重试(loop)、风控(ALLOW/DENY/CHALLENGE)
  • 明确错误计数与窗口期:输错 N 次锁定、冷却时间、验证码有效期
  • 重复提交有解释:requestId 去重/幂等
  • token/会话交代清楚:access/refresh、TTL、存储、撤销
  • 对外提示与日志原因分离:避免泄露风控规则
  • 关键超时写出来:短信通道、风控调用、登录接口整体超时
  • 有一个“可测试”的视角:每个 alt 分支都对应至少 1 条用例

如果你把这 10 条都做到,时序图基本就能过大多数评审。

14. 用“文字交互”快速生成可交付时序图(并导出到文档)

很多团队的问题不是不会画,而是“画图太慢 + 改动太频繁”。更实用的做法是:先把交互写成短清单,再把它转成图,改动时只改清单。

如果你想把上面的“骨架”更快落成图,可以试试这个在线时序图生成器:

它比较适合研发/测试写文档的工作流:

  • 左侧是编辑器,你可以**点选生命线/消息/组合片段(alt/loop/opt)**快速插入结构,避免手打语法
  • 右侧是实时预览,分支补齐时不会“画着画着崩掉”
  • 需要进 PRD/设计文档时,可直接导出 SVG/PNG/JPEG,也能导出 draw.io 继续协作
  • 如果你懒得从零写,也可以用 AI 生成:把“登录流程文字描述 + 约束条件(重试/风控/锁定)”贴进去,让它先出一版骨架,再由你校准消息命名与边界

注意:不管用什么工具,关键仍是“分支补齐 + 边界清晰”。工具只是把体力活省掉。

15. FAQ:画登录时序图时大家最常问的 8 个问题

Q1:验证码要不要画到“短信通道”这么细?

如果你的目标是研发实现/测试验收,建议至少画到“Auth 调用 SMS Provider 并处理失败”,否则你无法表达:通道失败怎么降级、频控在哪里做、日志怎么打。

如果只是课堂作业或概念说明,可以省略 SMS Provider,把它合并进 Auth,但要在图旁注明“短信发送细节省略”。

Q2:风控一定要单独一根生命线吗?

当你们有独立风控平台/规则引擎时,强烈建议单独画。哪怕风控逻辑现在写在 Auth 里,也建议概念上拆出来:这样未来演进(接第三方风控、拆服务)不会重画整张图。

Q3:登录失败原因要不要在图里写得很细?

对外不建议写细(防枚举),但对内建议细。最好的做法是:

  • 返回给 Client 的错误码可以合并(例如“登录失败/操作频繁”)
  • 但在图里用 Note 标出“内部日志原因码”,例如 risk_deny_blacklistotp_wrong_exceed,便于排障

Q4:loop 画在哪里最合理?

用户重试通常包裹“提交登录 → 校验 → 返回结果”这一段;不要把“获取验证码”也包进去(那是另一个 loop:发送频率)。

Q5:要不要把“加密/签名/HTTPS”画出来?

一般不画到 TLS 握手那种程度,但建议至少在关键请求旁注明:

  • 是否要求 nonce/timestamp/signature
  • 是否要求设备指纹字段

这对安全评审很有价值。

Q6:登录接口的超时怎么表达?

你可以在 Gateway → Auth 的消息旁写注释:timeout=3s。同时对下游(风控、短信)注明自己的超时与重试策略。

Q7:第三方登录(OAuth)是不是另一张图?

通常是另一张。因为参与者会变(OAuth Provider、redirect、code exchange),分支也不同。把它塞进同一张图会让读者痛苦。

Q8:我已经有流程图了,还需要时序图吗?

如果登录涉及:风控、验证码、重试、锁定、token 生命周期、多端策略——时序图更适合把风险与边界讲清楚。流程图更适合讲“业务步骤”。两者并不冲突。

16. 最后给你一个“评审通过版”的最小目标

当你下次要交付登录时序图时,把目标定成这句话:

  • “任何一个研发能根据它实现;任何一个测试能根据它写出覆盖主要分支的用例;任何一个架构/安全能在图上找到风控、锁定、token 撤销的控制点。”

做到这点,你画的就不是“看起来像”,而是“真正能用”。

如果你想把你们现有登录流程(文字/接口列表/日志字段)快速变成一张可交付的序列图,也可以直接用生成器先出骨架再改: