news 2026/9/26 5:33:38

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

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
扫码登录原理拆解:状态机、轮询与多端会话设计

“先别急着背八股,我把扫码登录拆开揉碎给你看。”

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,并把状态更新为“已确认”(CONFIRMED);PC 端下一次轮询发现状态变更,拿着 token 完成登录跳转。

如果用户在扫码后点了取消,状态则变成“已取消”(CANCELED)。如果超过过期时间还没有完成确认,状态就是“已过期”(EXPIRED)。把这几个状态完整说出来,面试官基本就能确定你不是只会背流程。

2.2 二维码里的内容与状态机设计

二维码里放什么,这里有个很容易被忽略的设计决策。很多初次实现的人会把 qrId 直接拼接用户信息放进去,这是错误做法。二维码内容永远只应该放一个一次性凭证(qrId 或加了签名的 token),绝对不要把 user_id、手机号这类信息放进去。原因很好理解:二维码是可以被拍照转发、被任意 App 扫描的,如果里面带了用户身份信息,就等于把个人敏感信息散播到了不可控的地方。

状态机的设计同样要非常小心。我见过不少团队把状态表设计成只有“未扫/已扫/已确认”三个值,结果线上出了“手机端显示已确认、PC 端还在转圈点击登录没反应”这种问题,排查半天才发现是状态只有一个字段、被并发请求覆盖了。合理的状态设计应该是:

  • WAITING:二维码创建成功,等待手机扫码
  • SCANNED:手机已经扫到码,等待用户点击确认
  • CONFIRMED:用户已确认登录,PC 端可以换取 token
  • CANCELED:用户主动取消
  • 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?qrId=xxx:查询二维码当前状态,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(seconds=120) record = QrRecord(qr_id=qr_id, status=WAITING, expire_at=expire_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(token=token, user_id=record.user_id, device_fingerprint=device_fingerprint) db.update(record, status=USED) # 一把二维码只能用一次 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 端怎么收到状态变化。这个过程玩明白了,面试基本不会再有障碍,线上排查也就有了方向感。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/26 5:29:28

SpringBoot+Vue3图书管理系统全栈实战:架构、数据库与部署

开头部分一个完整的图书管理系统&#xff0c;是Java程序员成长路上绕不开的经典项目。这次我打算把 SpringBoot Vue3 MyBatis MySQL 这套前后端分离的“智慧图书管理系统”源码&#xff0c;从技术选型到落地部署&#xff0c;全部拆开讲清楚。不管你是准备做毕业设计、课程设…

作者头像 李华
网站建设 2026/9/26 5:29:10

Spring DataSource配置全攻略:从XML到Boot连接池实战

说实话&#xff0c;DataSource 这层配置我见过太多人栽跟头了。你说它难吧&#xff0c;表面看就是几行配置的事&#xff1b;你说它简单吧&#xff0c;线上连接池打满、慢查询拖垮整个应用、多数据源事务莫名其妙串库&#xff0c;这些问题十有八九都能追溯到 DataSource 的配置细…

作者头像 李华
网站建设 2026/9/26 5:28:54

开源本地化AI代码评审工具open-code-review实战指南

1. 项目概述&#xff1a;这不是又一个“AI写代码”玩具&#xff0c;而是一套可嵌入日常开发流水线的开源代码评审协作者“open-code-review”这个名字乍看平平无奇&#xff0c;甚至有点拗口——它既不像“Copilot”那样直击眼球&#xff0c;也不像“Cursor”那样自带产品感。但…

作者头像 李华
网站建设 2026/9/26 5:28:48

仲夏CMS | 搬得进,摆得正,取得回~五系统导入导出功能介绍

ZXSORA CMS-FIDELITY-20260925搬得进&#xff0c;摆得正&#xff0c;取得回 五系统导入导出功能介绍先摆问题&#xff0c;再论矛盾&#xff0c;动手解&#xff0c;最后让实践说话——每一步都配当场拍的截图。5 源系统 28 篇零丢失 113 项检查 0 失败 25 附件原名 5 条回环…

作者头像 李华
网站建设 2026/9/26 5:28:05

AgentScope 2.0 多智能体协作实践:从架构设计到Java企业级落地

如果你最近在调研多智能体开发框架&#xff0c;大概率绕不开 AgentScope 这个名字。我是在一次内部项目里第一次接触它&#xff0c;当时团队要把好几个大模型能力串成一条自动处理链路&#xff0c;试了一圈通用编排工具&#xff0c;最后还是回到 AgentScope。说实话&#xff0c…

作者头像 李华
网站建设 2026/9/26 5:27:57

逻辑回归鸢尾花三分类实战:从数据清洗到ROC评估与LaTeX报告

简介&#xff1a;本资源是一份面向高校机器学习课程初学者与期末大作业需求者的完整实践项目&#xff0c;聚焦逻辑回归算法在经典鸢尾花数据集上的分类应用。包内含可直接运行的Python源码&#xff08;含详细中文注释&#xff09;、结构清晰的实验报告&#xff08;含原理推导、…

作者头像 李华