ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

扫码登录原理拆解:状态机、轮询与多端会话设计

扫码登录原理拆解:状态机、轮询与多端会话设计 “先别急着背八股我把扫码登录拆开揉碎给你看。”2026年了我在面试里还经常遇到这样的对话问候选人“扫码登录的原理是什么”他能答出“前端轮询接口、后端生成二维码、手机扫码确认”但再往下追问“二维码过期时间是多久状态机怎么设计扫码之后 PC 端收到的 token 和普通登录的 token 有区别吗手机上显示‘登录两台设备’是什么含义”就开始支支吾吾。说实话扫码登录不是什么高深技术但它是一个极好的“一题多考点”面试题。它同时覆盖了 Web 状态管理、前后端异步交互、分布式会话、安全校验、多端登录态设计几乎每个模块都能继续深挖。这篇文章不打算写成教科书而是站在一个既面试过别人、也亲手做过企业级扫码登录系统的开发者的角度把扫码登录从“表面上看到的样子”一直拆到“你真正要动手实现的样子”。无论你是正在准备面试的候选人还是要在一套业务系统里落地扫码登录的后端或前端工程师这篇都能给你一套可以“抄作业”的参考。1. 扫码登录到底在解决什么问题1.1 面试官为什么总爱问扫码登录面试官问扫码登录表面上是考察你知不知道这个流程本质上是在观察你对“异步交互”和“状态机”这类问题的敏感度。一个功能从用户视角看只有几秒钟但背后至少要解决三件事怎么把一个 PC 端的登录请求安全地转移到手机端去确认确认结果怎么实时同步回 PC 端确认之后签发的凭证要怎么和这台设备绑定防止被拿走以后到处用所以候选人如果只能说出“轮询”两个字我基本可以断定他没做过真实项目只是在网上看过几篇文章。真正做过的人会告诉你轮询只是其中一环还要处理二维码过期、重复扫码、并发确认、设备指纹、token 与设备的绑定关系、以及扫码之后手机端和 PC 端状态不一致时怎么自愈。这些问题随便挑一个都足够考察一个人的系统设计能力。1.2 扫码登录的本质把“输入”变成“确认”账号密码登录的核心动作是“输入”扫码登录的核心动作是“确认”。输入是需要用户在 PC 端敲键盘的而 PC 端往往是不可信环境——可能是网吧电脑、公司公用机、酒店一体机。把密码输入到一台不受控制的设备上本身就是安全风险。键盘记录器、浏览器插件、伪装的登录页面都有可能截获密码。扫码登录的设计意图很直接PC 端不承担任何秘密输入功能它只负责展示一个二维码。真正验证身份的动作发生在用户自己的手机上而手机上的登录态是用户已经预先建立的比如微信、支付宝早就登录过了。这样密码不经过目标设备也就绕开了大部分密钥窃取风险。这是扫码登录在安全层面最重要的价值也是我面试时最希望听到的答案之一。1.3 和账密登录相比扫码登录解决了哪三个问题第一个是安全问题。密码不落在 PC 端降低被键盘记录器、恶意插件窃取的风险手机端通过已登录 App 完成确认等于做了一次“已持有设备”的强校验。第二个是体验问题。PC 端不用安装密码管理工具不用记住复杂密码掏出手机扫一下即可。对于一体机、智能电视、展厅大屏这类键盘输入困难的场景扫码几乎是唯一的合理登录方式。第三个是设备锚定问题。扫码登录天然地把登录行为绑定到了一台具体设备上因为二维码是一次性的、与某次会话绑定的扫码确认之后签发的 token 也可以额外绑定设备信息。这比单纯账密登录之后再造一个“设备管理列表”更自然因为扫码登录从第一步就把“设备”这个概念引入了。2. 扫码登录核心流程与关键设计2.1 一次完整扫码的四个阶段一次扫码登录从用户打开 PC 端登录页开始到成功进入系统结束通常经历四个阶段请求二维码、轮询状态、扫码确认、签发 token。第一阶段PC 端页面加载时向后端发起一个创建二维码的请求后端生成一个全局唯一的 qrId一般用 UUID并把它的状态初始化为“待扫码”WAITING同时设置过期时间。第二个阶段PC 端拿到 qrId 后开始轮询查询状态每两三秒请求一次。第三个阶段用户用手机 App 扫到二维码App 把 qrId 和手机端登录态一起提交到后端后端把状态更新为“已扫码待确认”SCANNED并在手机端展示用户头像和“确认登录”按钮。第四个阶段用户点击确认后端校验通过后签发 token并把状态更新为“已确认”CONFIRMEDPC 端下一次轮询发现状态变更拿着 token 完成登录跳转。如果用户在扫码后点了取消状态则变成“已取消”CANCELED。如果超过过期时间还没有完成确认状态就是“已过期”EXPIRED。把这几个状态完整说出来面试官基本就能确定你不是只会背流程。2.2 二维码里的内容与状态机设计二维码里放什么这里有个很容易被忽略的设计决策。很多初次实现的人会把 qrId 直接拼接用户信息放进去这是错误做法。二维码内容永远只应该放一个一次性凭证qrId 或加了签名的 token绝对不要把 user_id、手机号这类信息放进去。原因很好理解二维码是可以被拍照转发、被任意 App 扫描的如果里面带了用户身份信息就等于把个人敏感信息散播到了不可控的地方。状态机的设计同样要非常小心。我见过不少团队把状态表设计成只有“未扫/已扫/已确认”三个值结果线上出了“手机端显示已确认、PC 端还在转圈点击登录没反应”这种问题排查半天才发现是状态只有一个字段、被并发请求覆盖了。合理的状态设计应该是WAITING二维码创建成功等待手机扫码SCANNED手机已经扫到码等待用户点击确认CONFIRMED用户已确认登录PC 端可以换取 tokenCANCELED用户主动取消EXPIRED二维码过期不能再被使用这五个状态之间还应该有明确的转移规则。比如 WAITING 可以直接到 EXPIRED但 SCANNED 是否还能过期可以用户扫完码一直不点确认过期时间到了照样作废。这两个状态的处理逻辑不一样实现的时候要单独写清楚。2.3 轮询、WebSocket、SSE 怎么选扫码之后 PC 端怎么知道手机端已经扫码确认了业界常规做法有三种短轮询、WebSocket、SSE。我在真实项目里首选的是短轮询原因很简单第一实现简单一个 GET 接口就能搞定不需要维护长连接第二对基础设施要求低不需要额外的消息网关或负载均衡配置第三扫码登录是低频交互用户一次扫码到确认的窗口期通常只有几秒到十几秒2 到 3 秒一次的轮询请求量对服务器压力几乎可以忽略。WebSocket 和 SSE 当然能用但它们的优势主要体现在高频实时交互上。如果你的系统里同时已经有了一整套 WebSocket 基础设施那顺手用上没问题但如果为了扫码登录单独引入 WebSocket 链路反而增加了运维复杂度不值得。面试时如果能讲清楚“为什么我选轮询而不是 WebSocket在什么量级下这个选择会有问题”会比直接回答“我用 WebSocket 实现更快”更有说服力。2.4 多设备登录与免扫码场景的扩展注意“微信扫码登录电脑显示两台设备”这个真实场景手机上登录了一个微信账号PC 端在办公室和家里的电脑分别扫码登录过那手机端就会显示两个已登录设备。这说明扫码登录签发的不是“用户级别的全局 token”而是“设备级别的会话凭证”。每扫一次码后端会为那台设备生成独立的 session 或 token用户可以在手机端看到所有已登录设备并单个撤回。这个设计在企业系统里尤其重要。比如你是一家 SaaS 公司的开发者用户会在公司的 Windows、家里的 Mac、客户现场的 iPad 上扫码登录同一个账号。如果所有设备共用同一个 token一旦某个 token 泄露只能全端下线。但如果按设备维度管理登录态就可以只踢掉某台异常设备其他设备完全不受影响。顺便说一下“驱动总裁免扫码登录”这类变体。驱动总裁这类工具软件通常要运行在系统刚装好、驱动还没装全的环境里这种环境经常没有网卡驱动或没有浏览器缓存扫码登录并不现实。于是就有了“免扫码”方案用户可以先用手机账号登录后台生成一个一次性授权码或激活码再把它填进离线工具的登录框里。它本质上还是扫码登录那一套——先由手机端完成身份确认再把这个“已确认授权”的动作传递到目标设备——只不过传递媒介从二维码图片变成了手动输入的一串授权码。理解了这一点你就会发现所谓“免扫码”并不是脱离了扫码登录的设计思想而是把确认动作的承载方式换了一种形式。3. 手写一个最小可用的扫码登录3.1 数据结构与接口设计纸上谈兵没有意义我直接把一套能跑通的最小实现给你拆开看。后端语言不重要我用伪代码描述你换成 Java、Go 或 Python 都一样。第一张表是二维码记录表。qr_id 作为主键expire_time 记录过期时间status 记录状态机当前值另外有一个字段记录关联的用户确认信息user_id这个字段在扫码之前一直是空的等手机端扫码确认时才写入。第二张表是登录会话表。token 作为主键关联 user_id、设备指纹 device_fingerprint、创建时间和最后活跃时间。注意这里 token 不是一个简单的随机字符串而是一个至少 128 位的随机值用安全的伪随机数生成器生成不能是自增 ID。接口层面核心就四个POST /qr/create创建二维码返回 qrId 和二维码图片内容GET /qr/status?qrIdxxx查询二维码当前状态PC 端轮询调用POST /qr/confirm手机端提交扫码确认或取消POST /qr/exchange状态为 CONFIRMED 后PC 端用它换取正式 token有人会问为什么确认之后还要单独设计一个 exchange 接口不能直接把 token 放在 confirm 的响应里吗原因是时序问题confirm 接口是手机端调用的PC 端并不知道手机端什么时候成功获取了 token。就算你把 token 直接返回给手机端手机端也缺少安全通道把它转交给 PC 端。所以必须设计成“手机端确认状态PC 端轮询到状态后再主动换取 token”这才能保证 token 只会发到那个一直在轮询的 PC 端手里。3.2 核心流程代码示意创建二维码的后端逻辑很简单核心就是生成唯一 qrId 并落库def create_qr(): qr_id uuid4() expire_at now() timedelta(seconds120) record QrRecord(qr_idqr_id, statusWAITING, expire_atexpire_at) db.insert(record) return { qr_id: qr_id, qr_content: build_qr_content(qr_id), # 内容只包含 qr_id不含用户信息 expire_in: 120 }查询状态的接口注意两件事查询的同时检查过期时间不要依赖一个定时任务去批量把过期状态翻掉每次查询都把当前时间拿出来和 expire_at 对比如果已经超过就直接返回 EXPIRED。这种惰性过期处理简单可靠生产环境里也是主流做法。手机端确认的逻辑要考虑到并发和幂等def confirm(qr_id, user_id, action): with transaction(): record db.select_for_update(qr_id) if record is None or record.status not in (WAITING, SCANNED, CONFIRMED): raise InvalidQrCodeError() if record.expire_at now(): record.status EXPIRED db.update(record) raise QrExpiredError() if action confirm: record.status CONFIRMED record.user_id user_id db.update(record) elif action cancel: record.status CANCELED db.update(record)这里我用了 select_for_update目的就是为了锁住这一行记录防止手机端两个人同时扫同一个二维码、同时提交确认导致状态被覆盖。实际产品里这种并发情况不多但一旦出现用户感知就是“我明明扫了码却登录了另一个人”属于特别严重的体验事故宁可加一把行锁也不要省。最后是 PC 端换取 token 的逻辑def exchange(qr_id, device_fingerprint): record db.query(qr_id) if record.status CONFIRMED: token secrets.token_urlsafe(48) db.insert_session(tokentoken, user_idrecord.user_id, device_fingerprintdevice_fingerprint) db.update(record, statusUSED) # 一把二维码只能用一次 return token return None注意这里我把状态又推进了一个值USED。因为 CONFIRMED 之后 PC 端的轮询可能请求多次如果每次拿到 CONFIRMED 就发一个新 token就会产生一堆无效会话。标记成 USED 之后只有第一次 exchange 能成功后面的请求会拿到空结果。3.3 几个关键参数与安全细节参数设置不要拍脑袋我按实际经验给你一个参考值二维码过期时间 120 秒是体验和安全比较平衡的选择。如果太短比如 30 秒办公网慢一点的用户刚掏出手机还没来得及扫就过期了会被骂的如果太长比如 10 分钟二维码被截图转发后长时间有效安全隐患会显著上升。轮询间隔我建议 2 到 3 秒。小于 1 秒会让服务器无谓地多受几倍请求大于 5 秒会让用户觉得“扫完等了半天没反应”。另外还有一个技巧PC 端轮询如果连续收到三次网络错误不要继续闷头重试应该提示前端网络异常并停止轮询避免用户在不稳定的网络环境下疯狂请求。安全上还有三个细节值得多说一句。第一二维码内容最好加一个签名比如使用 HMAC 对 qrId 签名手机端携带签名上报防止有人伪造一个 qrId 去探测后端是否存在这样的二维码。第二手机端扫码后展示的“确认页”必须显示账号昵称和头像让用户看到自己在授权的具体是哪个账号很多钓鱼工具就是让用户扫了码看到一片空白以为没扫上结果账号已经被恶意绑定。第三签发 token 时要把 device_fingerprint 一起绑定进会话记录每台设备一个会话互不共享。4. 面试高频问题与线上排查实录4.1 面试官最爱追问的五个问题我在面试中会围绕扫码登录连续追问几个题目这里有真实时间里的高频版准备面试的人可以逐条自查第一个二维码过期之后 PC 端界面应该出现什么如果前端一直轮询到一个 EXPIRED 状态正确做法是立刻停止轮询提示用户点击“刷新二维码”并且保证新二维码的 qrId 和旧的不一样。很多人会忽略一个细节旧的二维码被扫过之后再点击刷新页面上如果还短暂显示旧二维码残留用户容易扫错所以刷新时应该先清空画布再请求新码。第二个用户扫完码点了确认但 PC 端一直没反应最可能的原因是什么优先检查轮询是否由于页面切换导致定时器被挂起其次是后端状态流转是否正确最后看网关层是否把轮询请求缓存了。线上这类故障八成出在缓存上一些网关默认会把 GET 请求做缓存一旦缓存命中PC 端永远查不到状态变化。第三个二维码可不可以让手机端直接拿到 token 后再传给 PC 端这恰好是那道“时序题”的深入变种你需要清楚说明为什么 token 不能从手机端回传以及为什么必须由 PC 端主动换取。第四个用户被要求在手机上“点击确认”这个确认动作算不算一次独立的身份验证实际上它是“持有手机”与“用户主动操作”的双重校验比单纯登录态校验多了一层防误扫和防自动授权的语义。第五个同一个二维码可以被两台手机同时扫吗正确设计是不可以。第一次扫码后状态变为 SCANNED此时第二台手机扫码时要么返回“二维码已被扫码”的提示要么直接忽略。很多系统为了体验更好会在 SCANNED 状态下允许手机端再次展示确认页但最终只有第一个提交确认的手机能成功。4.2 线上故障排查的真实案例讲一个我实际踩过的坑。有一版扫码登录上线后客服反馈部分用户扫码后手机端能正常显示确认页但 PC 端一直在转圈。排查链路是这样的先看轮询请求是否到达后端——日志显示 PC 端的轮询请求压根没有打进来说明问题出在浏览器或者接入层。再看浏览器发现用户的电脑时间比服务器快了整整三分钟会导致 HTTPS 请求里的时间校验失败吗不会因为浏览器与服务器之间的 TLS 握手用的是各自的时间戳结果发现是接入层在做请求校验时把“客户端时间与服务器时间差超过阈值”的请求直接拦截了。这个问题的根因就是扫码登录轮询请求里带了时间戳签名但用户系统时间不准。解决方式是把时间校验从硬校验改成宽松策略同时给 PC 端加一个“网络时间校准”的前置检查。那段时间我们还在客户端埋了个点凡是轮询异常的都上报系统时间偏差后来发现办公网里老电脑的时间偏差问题比想象中普遍得多。另一个案例是二维码图片本身。有一版我们把二维码图片放在 CDN 上结果 CDN 因为过期缓存导致用户看到的二维码其实是一个几十秒前的旧二维码扫出来的 qrId 已经过期。这个问题表面上是“二维码已过期”提示实际上根因是 CDN 缓存策略没针对动态二维码做禁用缓存。后来我们把二维码接口的响应头显式设置了 Cache-Control: no-store问题彻底消失。所以要总结一条排查经验扫码登录出问题不要一上来就看后端状态机先按“用户看到的二维码 → 轮询是否发出 → 后端是否收到 → 状态是否更新 → token 是否签发”这条链路逐段排查90% 的问题都能通过链路日志快速定位。5. 自研与第三方方案怎么选如果你是在真实项目里落地扫码登录还会面临一个决策自研实现还是接入微信开放平台、钉钉、企业微信这类第三方扫码登录我的建议是分场景。如果这是企业内部的 OA、HR 系统、运维平台优先考虑企业微信或钉钉的扫码登录。原因不是技术难度而是账号体系问题——企业内部系统本来就要统一账号源第三方扫码登录直接对接组织架构和成员状态省去了手机号验证、离职员工账号回收这一大堆事情。第三方平台通常还提供“扫码后展示员工姓名与部门”的能力跟企业内部信任模型天然匹配。如果是面向 C 端用户的网站或 App我建议认真评估自研。自研的最大收益是登录态完全在自己手里token 的签发、续期、撤回、风控都可以定制不需要依赖第三方 App 是否已登录。另外一个现实问题是C 端用户不一定装了你指定的那个 App强制要求“请先下载 XX App 扫码”本身就有很高的用户流失成本不如直接让用户用手机号验证码或账密登录。自研扫码登录的成本并不高像前面写的那套核心流程一套下来大概一个后端加一个前端各投入两三天就能跑通难的是后续的稳定性和安全性打磨。如果只是内部小工具完全可以先用轮询方案快速上线等并发量上来后再考虑要不要引入消息推送、长短连接切换等优化。别为了一个日均几百次扫码的功能自己去搭一套 WebSocket 网关那属于典型的过度设计。我记得之前在一家电商公司做促销后台的扫码登录最开始就是轮询每天大促时几十万次轮询请求后端一个普通服务完全扛得住。后来为了“技术更先进”换成了 WebSocket反而因为网关升级频繁出问题最后又切回了轮询。稳定压倒一切不是没有道理。6. 写在最后的个人体会面试面多了会发现扫码登录是一个特别诚实的题目。它不考验记忆力和背诵能力候选人只要真的动手做过哪怕只是一个小 demo都能在状态机、并发、安全这些细节里露出真实的技术功底。反过来没做过的人即使把八股文背得再滚瓜烂熟被问到“token 为什么不能让手机端拿”这种交互时序问题时也会露馅。我自己后来在项目里做了一个小的改进把二维码的内容和业务来源绑定比如一个二维码不仅用于登录还可以用于绑定设备、授权第三方应用。只要状态机设计得清晰一套扫码交互组件可以在公司内部复用很多次。这也是我强烈建议读者不要满足于“实现一遍”的原因——把扫码登录吃透了你就顺带把异步交互、状态管理、防并发冲突、安全校验这些通用的后端基本功都练了一遍。如果文章里的某个细节你还没想明白建议直接打开电脑写一个最小 demo给自己生成一个二维码用手机扫一下然后在浏览器里模拟确认观察 PC 端怎么收到状态变化。这个过程玩明白了面试基本不会再有障碍线上排查也就有了方向感。
返回列表