登录时序图怎么画:验证码/失败重试/风控校验分支怎么补齐
从研发/测试/架构视角讲清登录时序图的画法:先画主成功链路,再用 alt/loop/opt 补齐验证码、失败重试、风控挑战、锁定与降级等分支,并给出可交付的检查清单与常见反例修正。
如果你正在画“登录时序图”,最容易踩的坑不是箭头画法,而是分支不全:验证码要不要画?失败重试怎么表达才不歧义?风控(人机/设备指纹/黑名单/二次校验)插进来后,主链路怎么保持清晰?
这篇文章给你一个可落地的方法:
- 先画一条“可验收”的主成功链路(能对照接口与日志逐步验证)
- 再用 alt/loop/opt 把“必须补齐的分支”补上(验证码、失败重试、风控挑战、锁定/冷却、短信/邮箱发送失败等)
- 最后用一张检查清单保证你交付的时序图既能让研发实现,也能让测试写用例、让架构评审看风险
下面默认你画的是 UML 序列图(时序图),读者主要是产品/研发/测试/架构/写文档的人;学生作业也能直接套用。
1. 先把“登录”说清楚:你到底画的是哪一种?
“登录”至少有 4 种常见形态,你不先定范围,时序图必然越画越乱:
- 账号+密码(可能带图形验证码)
- 短信/邮箱验证码(一次性验证码 OTP)
- 第三方 OAuth(微信/企业 SSO/LDAP/SAML)
- 二次认证(MFA:短信/邮箱/Authenticator/硬件 Key)
建议你在图的左上角用一句话写范围(相当于图的“契约”):
- “本图描述:移动端手机号+短信验证码登录,包含失败重试与风控挑战;不包含注册与找回密码。”
这样评审时就不会有人问“那忘记密码呢?”然后强行把你图拖成一坨。
2. 登录时序图为什么总画不完整:因为大家把它当成流程图
很多人画出来的是“用户点按钮→后端返回 token”,看起来对,但不专业。原因是把时序图当成了“步骤列表”。
时序图真正擅长表达的是:
- 对象之间如何交互(谁调用谁)
- 同步/异步边界(哪里必须等,哪里可以异步)
- 失败如何处理(重试、回退、补偿、锁定)
- 安全控制点(验证码、风控挑战、签名/nonce、会话与 token 的生命周期)
所以画登录时序图的核心不是“把步骤写全”,而是:把交互点与控制点画全。
3. 开始画之前,先问 7 个问题(决定你的参与者与分支)
在你动手拉生命线之前,先把下面 7 个问题回答出来,哪怕只写在草稿里:
- 客户端是谁?Web / App / 小程序 / H5?是否有 BFF(Backend For Frontend)?
- 认证中心是谁?是独立 Auth Service,还是在 User Service 里?
- 凭证是什么?密码?短信码?OAuth code?
- token 体系是什么?Session Cookie?JWT?Access/Refresh?
- 风控在哪里?在网关?在 Auth Service?是外部风控平台?
- 失败策略是什么?密码输错次数?短信码错误次数?冷却时间?锁定策略?
- 观测点在哪里?登录失败原因码、风控命中、验证码触发、发送短信失败、延迟等指标是否要打点?
你会发现:这些问题一旦确定,参与者(生命线)就“自然长出来了”。
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. 先画“主成功链路”:不带分支、不带重试,只画一条能跑通的直线
以“手机号+短信验证码登录”为例,主成功链路可以这样简化:
- 用户输入手机号,点“获取验证码”
- Client 调用 Gateway:发送验证码请求
- Gateway → Auth:请求下发 OTP
- Auth → Risk:评估是否允许发送(频率、设备、黑名单)
- Auth → SMS:发送短信
- 用户输入短信码,点“登录”
- Client → Gateway:提交手机号+短信码
- Gateway → Auth:校验 OTP
- Auth → User:读取用户状态(是否禁用/锁定)
- Auth:签发 access/refresh token(或 session)
- Gateway → Client:返回 token
- 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)——“重试”不是一句话
“失败重试”在登录里有三层含义:
- 用户重试:用户反复输入密码/验证码
- 客户端重试:网络抖动导致的重发请求
- 服务端重试:调用下游(短信、风控、DB)的重试与熔断
在时序图里,建议把“用户重试”与“系统重试”分开表达,否则会出现歧义。
7.1 用户重试:用 loop 表达“最多 N 次”
示例(概念表达即可,不必拘泥语法):
loop [最多 5 次 / 10 分钟窗口]- Client → Gateway:
submitLogin(phone, otp) - Gateway → Auth:
verifyOtp(...) alt [otp_wrong]→ 返回错误码 + 剩余次数
- Client → Gateway:
关键点:把“窗口期”写出来,否则测试无法写出等价用例。
7.2 客户端重试:必须考虑幂等与重复提交
移动网络下,客户端可能因为超时“以为没提交成功”,于是重发。
你至少要在时序图里体现一个控制点:
- Client 每次提交带
requestId(或nonce) - Auth/Token Store 做去重:
dedupe(requestId)
否则你会在评审时被问:重复提交会不会发两次短信?会不会签发多个 token?会不会触发错误计数?
7.3 服务端重试:通常用 opt 标注“可能重试/降级”,并注明超时边界
例如短信通道:
opt [sms_timeout]:Auth 切换到备用通道或重试一次- 同时在消息旁标注超时:
timeout=2s、maxAttempts=2
你不必把每一次重试都展开成 10 条箭头,但要把策略与边界写清楚。
8. 必补分支 ③:风控校验怎么插入才清晰(不要把风控画成“一个判断框”)
风控常见的两类介入点:
- 发送前风控:是否允许发送 OTP(避免短信轰炸)
- 登录前风控:是否允许签发 token(避免撞库成功后直接拿到会话)
推荐画法:
- 把 Risk Engine 画成独立生命线
- 在两处调用:
riskEvaluate(sendOtp)与riskEvaluate(login) - Risk Engine 返回“决策对象”而不是布尔值,例如:
ALLOWDENY(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
主链路(成功):
- User → Client:输入手机号
- Client → Gateway:requestOtp(phone, deviceId)
- Gateway → Auth:requestOtp(…)
- Auth → Risk:riskEvaluate(scene=sendOtp, …)
alt [CHALLENGE]- Auth → Gateway:needChallenge(type=captcha)
- Gateway → Client:showChallenge
- Client ↔ Captcha:completeChallenge
- Client → Gateway:submitChallengeToken
- Gateway → Auth:verifyChallengeToken
alt [ALLOW]- Auth → SMS:sendSms(…)
- SMS → Auth:sendResult(ok)
- Auth → Log:logOtpSent
- User → Client:输入验证码
- Client → Gateway:submitLogin(phone, otp, requestId)
- Gateway → Auth:verifyOtp(…, requestId)
- Auth → Risk:riskEvaluate(scene=login, …)
alt [DENY]→ 返回失败(模糊提示 + 记录原因)alt [ALLOW]- Auth → User:loadUser(phone)
alt [locked/disabled/not_found]→ 返回失败- Auth → TokenStore:storeSession/refreshToken
- Auth → Gateway:issueToken(access, refresh)
- Gateway → Client:loginSuccess
- 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_blacklist、otp_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 撤销的控制点。”
做到这点,你画的就不是“看起来像”,而是“真正能用”。
如果你想把你们现有登录流程(文字/接口列表/日志字段)快速变成一张可交付的序列图,也可以直接用生成器先出骨架再改: