
摘要二维码登录几乎是每个互联网大厂面试都可能追问的基础设施题看似简单实则串联了二维码编码、会话管理、长连接、状态机、令牌交换和安全风控一整条链路。本文从二维码本身讲起把 PC 扫码登录拆成四个阶段重点剖析轮询与推送、服务端状态机、临时票据换会话令牌等核心机制并补上安全性设计、与淘宝手淘类似场景的工程推演、面试追问和一份可运行的 Java 示例。开场这个面试题到底在考什么很多人第一次被问到“说说二维码登录的原理”时脑子里第一反应是不就是一个二维码手机扫一下电脑就登录了嘛有什么好说的但如果你真的这么回答基本就凉了。这道题在大厂二面出现的频率非常高它不是一道背定义题而是一道链路题。面试官想听到的不是“生成二维码、扫码、确认”这三句话而是你能不能把下面这些点串起来二维码本身承载的是什么信息为什么不能把密码或会话令牌直接塞进去。PC 端和手机端之间不直接通信那 PC 端是怎么知道“已扫码”“已确认”这些状态变化的。服务端怎么维护二维码和会话之间的对应关系状态机如何设计。手机确认登录后PC 端凭什么能拿到一个已经登录的会话这一步的安全边界在哪里。二维码被拍照转发怎么办过期时间怎么定怎么防止被劫持或重放。换句话说这道题考的是分布式场景下的会话管理、消息协同和风控意识。下面我会从最底层的二维码讲起再一步步推到完整链路最后给出一个可以直接拿去面试的回答框架以及一段 Java 实现的核心逻辑。文章比较长但每一步都尽量说透读完你应该能从“我懵了”变成“我能反杀”。一、先别急着说登录搞清楚二维码到底是什么二维码登录的主角当然是二维码但很多同学对二维码的理解停留在“黑白格子扫了能打开东西”的层面。要讲清楚扫码登录第一步必须回答二维码里到底装了什么。1.1 二维码是一段被“可视化”的字符串二维码尤其是我们最常用的 QR Code全称 Quick Response Code。它本质上是一种二维条码作用就是把一段文本、数字或二进制数据编码成黑白相间的图形。扫码的过程就是手机摄像头拍摄图形经过图像处理和纠错解码把图形还原成原始字符串的过程。这里有一个非常关键但容易被忽略的结论二维码只是一个编码载体它是离线的、静态的。二维码本身不会和服务器通信也不会知道“有没有人扫我”。你生成一张二维码里面编码的内容就已经固定了谁扫、什么时间扫、用什么设备扫二维码自己一概不知。二维码就像一张写了内容的纸条只不过这张纸条是用黑白格子写成的需要特定的解读器才能读出来。1.2 QR Code 的结构与容错为了后面理解安全设计我们有必要简单看一下 QR Code 的结构。一张标准的 QR Code 主要由以下几个部分组成定位图形也就是三个角上的大方块回字图案用来让扫描器快速找到二维码的位置和方向。无论你正着扫、倒着扫还是斜着扫都能被定位。校正图形帮助扫描器在二维码较大、图像有畸变时进行校正。时序图形黑白相间的细线用来确定格子的密度和坐标。数据区域真正存放编码内容的地方以黑白模块表示 0 和 1。格式信息与版本信息记录纠错等级、掩码模式和版本号。QR Code 支持四个纠错等级L、M、Q、H。最高等级 H 可以恢复大约 30% 的码字损坏。这意味着即使二维码有一部分被遮挡、污损仍然可以被正确读取。这也是为什么很多 App 的二维码中间可以放一个 Logo而依然能正常识别因为被 Logo 挡住的部分可以通过纠错还原。1.3 扫码登录用二维码承载的是什么既然二维码只是字符串的图形化那么扫码登录的关键就在于这张二维码里编码的字符串究竟是什么。常见的做法是二维码里编码一个 URL这个 URL 指向服务端的一个扫码确认接口并且携带一个唯一标识比如一个随机的、不会重复的场景值例如https://login.taobao.com/scanConfirm?sceneId8f3a9c1e-2b7d-4f6a-9e0c-7d5b3a1f0e2d这个sceneId有的叫二维码 ID、扫码会话 ID、临时场景值名字不同但作用一致它把“这一张二维码”和“服务端这一次待登录请求”绑定起来。这里要特别强调二维码里通常不会直接放用户的账号、密码、会话令牌或者手机号。原因至少有两点二维码内容人人可见任何人只要扫一下或者用工具解析就能读出里面的字符串。如果把敏感信息直接放进去等于明文暴露。登录成功的会话令牌是具有强权限的东西一旦放进二维码被传播、被截图就可能被别人捡走直接使用风险极高。所以二维码里放的只是一个“一次性、无实际权限、可过期”的场景标识。真正的安全状态保存在服务端通过这个场景标识关联起来。这是整个二维码登录安全模型的起点。二、三维世界二维码登录的三个参与者理解了二维码本身之后我们再来看整体架构。一次典型的 PC 端扫码登录至少涉及三个角色PC 端浏览器用户想登录的设备。它负责展示二维码并等待登录结果。手机端 App通常是已经在后台处于登录态的 App用户用它的扫一扫功能扫描 PC 上的二维码。服务端登录系统的核心负责生成二维码、维护扫码会话状态、校验手机端身份、签发最终登录凭证。有一点要特别强调PC 端和手机端之间通常没有直接的网络连接它们不建立点对点通信。手机扫的只是屏幕上的一张图片手机读到的是图片里面编码的字符串。PC 端唯一能做的就是不停问服务端“我这个二维码现在状态变了没有”所以整个系统是一个围绕服务端的星型结构服务端是唯一的信息汇聚点也是唯一可信的状态持有者。理解了“PC 和手机不直连、都以服务端为中介”这一点二维码登录的很多设计就顺理成章了。下面我们用一句话概括整体链路PC 端向服务端申请一个临时场景并生成二维码手机扫描二维码拿到场景值后向服务端确认登录服务端确认手机身份后把这个场景标记为已确认PC 端通过某种方式感知到状态变化最后用这个场景兑换一个正式的登录会话。接下来我们把这句话拆开一个阶段一个阶段地讲。三、完整流程拆解四个阶段做到心里有数为了方便记忆和面试表述整个流程可以分成四个阶段请求生成、扫码匹配、确认授权、换取登录态。下面详细展开每一步。3.1 阶段一PC 端请求生成二维码当用户在 PC 网页上点击“扫码登录”时浏览器会向服务端发起一个请求相当于说“我要在这个页面上登录给我一个二维码。”服务端收到请求后一般会做这么几件事生成一个全局唯一、不可猜测的场景标识例如用 UUID 或者雪花算法生成并设置为较短的过期时间比如 1 到 2 分钟。在服务端的存储中创建一条扫码会话记录初始状态为“等待扫码”同时记录创建时间、过期时间、来源设备信息等。把这个场景标识拼接成扫码地址再编码成二维码图片返回给 PC 端展示。这里有两个细节值得追问为什么场景标识要不可猜测为什么要有过期时间不可猜测是为了防止攻击者枚举或伪造场景标识过期时间是为了防止二维码长时间有效被截屏后反复使用。后面安全章节会专门展开。3.2 阶段二手机扫码并与服务端匹配用户打开已经登录的手机 App用扫一扫功能对准 PC 屏幕。手机识别二维码后拿到里面的 URL 和场景值。随后手机 App 会携带自己的登录态比如已经存在的 accessToken 或会话 Cookie向服务端发起“确认扫码”请求报文里包含场景值。服务端收到请求后要做两件关键事情校验场景值是否存在、是否未过期、是否还处于“等待扫码”状态。如果场景已经被使用、已失效或被篡改直接拒绝。校验手机端的登录态是否有效也就是确认“这台手机上的这个用户确实是一个已经登录的合法用户”。这一步是整个扫码登录信任链的根基。校验通过后服务端并不立刻让 PC 登录而是把这条扫码会话的状态从“等待扫码”更新为“已扫码、待确认”同时把手机端的用户信息临时绑定到这个场景上。此时 PC 页面的二维码通常会变成“已扫码请在手机上确认”。3.3 阶段三手机端确认授权手机扫码成功后会跳转到一个确认页面页面展示“即将在电脑上登录账号 XXX”并询问用户是否确认。用户点击“确认登录”后手机 App 再次向服务端发起一个“确认”请求携带场景值和手机端的登录态。服务端再次校验场景状态是否为“已扫码、待确认”再校验手机登录态全部通过后把扫码会话状态更新为“已确认”并为这个场景生成一个一次性、短时有效的临时票据比如一个随机 ticket。这个 ticket 不能直接当作长期会话使用它更像是一张“换取登录凭证的兑换券”。3.4 阶段四PC 端感知并换取登录态PC 端从展示二维码开始就在不断地向服务端查询这个场景的状态。当它查询到状态是“已确认”时就可以带着场景值向服务端发起“换取登录凭证”的请求。服务端校验场景值是已确认状态并且这次兑换还没有被用掉然后完成三件事为 PC 端当前浏览器创建一个正式的登录会话下发 session、Cookie 或者 accessToken 与 refreshToken。把这个扫码场景标记为“已完成”或者直接删除确保一次性使用防止同一个二维码被重复兑换。把下发的登录凭证与 PC 浏览器绑定必要时记录设备信息供后续风控使用。PC 端拿到凭证后跳转到登录成功页整个流程结束。这就是二维码登录的主干。你会发现真正承载登录态的不是二维码而是服务端维护的这条“场景会话”以及最终签发的那份凭证。二维码自始至终只是传递了一个场景标识。四、二维码编码内容细节别把令牌塞进去前面的流程里反复提到二维码里放的是场景标识。这里我们再单独用一节来说明为什么不能把敏感信息直接写进二维码以及实际工程中二维码内容是怎么设计的。4.1 场景值与二维码内容的关系在生成二维码时常见的内容格式是一个包含场景值的 URL。URL 的域名通常是登录服务统一的域名路径可以是扫码确认接口查询参数中携带场景值。手机扫码后App 会识别出这是自家的扫码登录链接而不是普通的网页链接然后走 App 内部的确认逻辑。为什么用 URL 而不是一段裸的随机字符串因为扫描器扫描二维码时通常需要区分二维码的用途。用标准 URL 能让 App 通过 scheme 或域名识别出“这是扫码登录场景”并路由到正确的内部处理逻辑。此外URL 还可以承载更多结构化参数便于后续扩展比如携带来源标识、风控参数等。4.2 为什么不能直接放账号或令牌这是一个很好的高频追问。直接放账号或令牌至少有这些问题二维码是对所有看到它的人公开的。如果里面是账号信息截屏就可能泄露账号。如果里面是已经签发的登录令牌拿到这张图的人就相当于拿到了这个令牌可以直接在其他设备上登录等于把钥匙印在了海报上。登录令牌通常有较长有效期而二维码需要短时失效两者生命周期不匹配。把长有效期的令牌放进一个短时场景里难以做到最小权限。因此正确做法是二维码内容无实际权限只是一把“查询钥匙”。真正的判断和授权全部在服务端完成。这样即使二维码被转发对方也必须先拥有一个已经登录的账号来确认否则无法完成登录。4.3 场景值的生成要求场景值的生成至少需要满足全局唯一、随机不可预测、足够长度、带过期时间。工程上常用 UUID v4或基于加密安全的随机数生成器生成的随机字符串。不要使用自增 ID否则容易被遍历。存储时通常以场景值为 keyvalue 是一个包含状态、用户信息、过期时间、设备信息的结构。五、最难也最常被追问的点PC 端怎么知道状态变了前面埋了一个问题手机扫码、手机确认这些动作都发生在手机上、请求都发到了服务端那 PC 浏览器是怎么知道的它的页面为什么能从“等待扫码”变成“已扫码”再变成“登录成功”这就是这道题的深层考点客户端与服务端之间的状态同步机制。因为 PC 端和手机端不直接通信PC 端只能从服务端获取状态。获取方式通常有四种普通轮询、长轮询、WebSocket、SSE。下面逐一分析并给出对比。5.1 普通轮询普通轮询就是 PC 端每隔一定时间向服务端发一次请求问“状态变了吗”。比如每 2 秒查询一次。服务端每次立即返回当前状态不管有没有变化。优点是实现简单兼容性最好。缺点是浪费请求大多数查询返回的都是“没变化”而且实时性受轮询间隔限制间隔太短又会对服务端造成压力。在二维码场景中扫码动作本身是低频的普通轮询在用户量不夸张时是可用的但大规模场景下请求量会非常大。5.2 长轮询长轮询是对普通轮询的优化。PC 端发起一个请求后服务端并不立即返回而是把这个请求挂起一段时间比如 25 到 30 秒。在这段时间里如果状态发生变化立即返回新状态如果一直没变化等到超时后返回一个“无变化”PC 端收到后再发起下一次长轮询。这样在状态没有变化的大部分时间里请求数量大大减少而状态一旦变化又能较快地通知到 PC 端。长轮询需要注意超时时间、连接数和中间代理的超时限制但整体上比普通轮询更优雅也是很多系统在过渡阶段的选择。5.3 WebSocketWebSocket 是一种全双工长连接协议。PC 端加载登录页后与服务端建立一个 WebSocket 连接。手机确认登录后服务端可以主动向这个连接推送“已确认”的消息PC 端收到推送后立刻换取登录态。WebSocket 的最大优势是真正的事件驱动、低延迟、服务端主动推送且不需要反复建立 HTTP 连接。但它对网关、负载均衡、连接管理和服务端资源有更高的要求需要处理心跳、断线重连、连接数上限等问题。对于扫码登录这种每个页面只持续一两分钟的场景WebSocket 并不是必须的但对于高并发、强实时性的系统它是更现代的选择。5.4 SSESSE即 Server-Sent Events是浏览器单向接收服务端推送的技术。它基于 HTTP由服务端通过一个长连接的响应流持续向浏览器推送数据浏览器使用 JavaScript 的 EventSource 接口接收。SSE 的优点是实现比 WebSocket 简单基于普通 HTTP天然支持断线重连适合服务端单向推送的场景。缺点是只能服务端到客户端单向通信如果要双向交互还需要配合普通 HTTP 请求。对扫码登录来说主要是服务端向 PC 推状态所以 SSE 也是一种可以采用的方案。5.5 四种方式对比方式实时性服务端压力实现复杂度适用场景普通轮询取决于间隔较差请求量大最低低并发、容忍延迟长轮询较高中等中等状态低频变化WebSocket高连接维护成本较高较高高并发、强实时性SSE高中等中等偏低服务端单向推送六、扫码登录的状态机服务端到底维护了什么如果你能一路讲到这里面试官心里其实已经给你打了及格分。但二面往往不会满足于流程描述他会继续往深挖服务端到底存了什么这些状态之间是怎么流转的为什么会设计成“等待扫码、已扫码、已确认”这样的分步状态而不是手机一扫就直接登录这一节就来回答扫码登录本质上是一个有限状态机服务端维护的不是二维码图片而是二维码背后这条会话的状态。6.1 一张二维码对应一条完整生命周期从服务端视角看每一次 PC 端申请二维码其实都是在服务端创建一个“扫码会话对象”。这个对象至少包含场景标识、当前状态、创建时间、过期时间、手机端用户信息、临时票据、来源设备信息、是否已兑换等字段。它的状态通常按下面的顺序流转等待扫码初始状态二维码刚生成还没有任何手机扫描。已扫码、待确认手机已经扫描且手机端登录态校验通过等待用户在手机上点“确认登录”。已确认用户在手机上点击了确认服务端已经生成一次性临时票据等待 PC 端来兑换。已完成PC 端使用临时票据成功换取了长期登录态这条场景被标记为已完成或直接删除。已过期二维码超过有效期或用户主动刷新场景失效无法再被扫码、确认或兑换。这里最关键的一个设计点是状态只能单向推进不能回退。一条已经“已确认”的场景不能重新变回“等待扫码”一条已经“已完成”的场景更不能再次兑换。这个单向约束是整个扫码登录防重放、防越权的核心。6.2 状态通常存在哪里这类临时状态显然不适合写进核心关系型数据库的正式用户表中。工程上最常见的做法是放进 Redis 这类高性能、支持过期时间的缓存中间件。以场景值为 keyvalue 是一个包含状态、用户标识、创建时间等信息的结构同时设置与二维码有效期一致的 TTL比如两分钟。使用 Redis 有几个天然好处读写性能足够高能扛住 PC 端频繁轮询带来的查询压力支持 TTL可以自动化处理过期场景数据结构简单适合表达短期、高频、一次性的状态。与此同时服务端仍然可以异步落一份日志或审计记录到持久化存储用于后续排查和风控分析但不把核心秒级状态读写在关系型数据库里做。6.3 为什么一定要分步设计状态机有的同学会问手机一扫直接登录不行吗为什么要多一个“确认”步骤从体验上看当然可以更短但从安全和职责边界上看多一步“确认登录”非常必要。手机扫码只代表“这台手机读取到了二维码内容”并不代表“用户已经明确同意在这台 PC 上登录”。如果扫码即登录那么误扫、扫到钓鱼二维码、把二维码截图发给别人都可能直接造成账号在未知设备上登录。把“扫码”和“确认”拆开相当于把“识别”和“授权”分开。扫码只是建立手机与场景之间的联系确认才是用户对授权的明确表达。很多账号体系还会在确认页展示登录设备名称、登录地点、登录时间让用户在授权前有机会判断“这台电脑是不是我自己的”。这正是状态机多一步的真正价值。七、安全设计扫码登录不是“扫到就安全”流程讲清楚之后二面面试官通常会把话题转向安全。二维码登录看起来比密码登录更“高级”但它自己也有全新的攻击面。你需要能主动说出风险并给出对应的防护思路而不是等面试官一点点问。下面按风险点逐一展开。7.1 二维码必须短时有效并且可刷新二维码里的场景标识是一次性的、临时性的因此二维码本身也必须有过期时间。常见有效期在一到两分钟。过期后即使有人拿着原来的截图继续扫服务端也会因为场景不存在或已过期而拒绝。为什么不能一天都有效因为二维码一旦被截图、拍照、转发有效期越长风险窗口就越大。短时有效本质上是在对“二维码可能已经被传播”这个假设做兜底。此外PC 页面通常还提供“刷新二维码”的入口。刷新动作在服务端对应的是把旧场景标记为失效创建一个全新的场景和二维码。旧二维码立刻失去作用这是一层非常直接的止损手段。7.2 一次一票防止重放手机确认后生成的临时票据必须是一次性的。PC 端用它换取登录态之后服务端要立即把场景标记为“已完成”或者删除确保同一个场景、同一个票据不能被第二次使用。这一步要特别注意并发问题高并发下两个几乎同时到达的兑换请求都可能读到“已确认”状态。工程上需要通过原子操作来实现“检查并置为已完成”例如 Redis 的原子命令、分布式锁或者数据库的唯一约束配合更新影响行数判断保证只有一个请求兑换成功。7.3 确认页必须展示关键信息防止误确认手机确认页不能只有孤零零一个“确认”按钮。至少要展示即将登录的账号、登录设备类型、来源入口。更严格一点的场景还会展示登录地点、浏览器或客户端标识。这样做的目的是让用户在授权前有一次“人工风控”的机会如果显示的是“正在登录 Windows Chrome地点厦门”而用户此刻人在北京、用的也不是这台电脑就能立刻拒绝。7.4 兑换接口要绑定发起方防止越权兑换临时票据本身不是万能钥匙。PC 端去兑换登录态时服务端不能只校验“场景值有效、已确认”还要校验这个兑换请求与最初创建二维码的 PC 端是否是同一个来源。常见的做法是在 PC 端第一次请求生成二维码时同时下发一个绑定当前浏览器的标识比如写入 HttpOnly Cookie 或一个短期校验值。兑换时同时校验场景值和这个绑定标识避免攻击者拿到场景值后从另一台设备抢单。7.5 设备识别与风险控制真实生产环境里还会引入风控层。比如同一账号短时间内在多个 IP、多台设备频繁扫码老的登录态和新的扫码动作发生地点跨度异常手机端登录态本身已经被标记为风险账号。风控系统可以在“扫码”“确认”“兑换”多个节点介入对异常行为增加二次验证、短信验证码、人脸识别或者直接阻断。一个好用的总结是二维码登录并没有消灭风险它只是把“静态密码”换成了“手机端已有登录态加用户主动确认”。真正的安全来自短期场景、单次票据、来源绑定和风控组合而不是二维码本身。八、淘宝手淘这类场景的工程推演文章标题里提到淘宝二面这里自然要谈一谈如果把扫码登录放到淘宝、手淘这类亿级用户场景里工程上会额外遇到哪些问题。需要说明的是不同公司的具体实现属于内部细节下面我们不讨论任何未公开的私密方案而是从通用的高并发系统设计角度做推演这也正是面试官想考察的能力。8.1 超大流量下的二维码生成与查询大型电商平台的登录页流量非常大尤其在大促期间。生成二维码不能成为系统瓶颈因此场景标识的生成要尽量无状态、可水平扩展比如使用随机 UUID 或分布式 ID而不是依赖单机自增或频繁写入同一个存储。查询状态的压力则需要通过 Redis 集群、合理分片以及更高效的实时通知方式来解决避免海量 PC 端同时短间隔轮询。8.2 用推送减少轮询压力在亿级请求场景中如果每个登录页都每两秒轮询一次哪怕大多数请求只是返回“没变化”累积起来也是巨大压力。因此大型系统倾向于在手机扫码、确认时通过内部的发布订阅或消息队列把状态变化实时推送给对应的 PC 连接。PC 端在长连接空闲时只需要处理心跳和断线重连不需要高频轮询。这样可以把“查询状态”从拉模型改成推模型大幅降低无效请求。8.3 多端、多登录体系的融合淘宝体系里有账号、支付宝、商家后台等多种身份概念登录入口也分布在浏览器、移动端、大屏设备等多个终端。扫码登录服务需要能够接入统一的账号体系和风控体系复用现有的会话签发、验签、设备管理能力而不是另起炉灶。这样当手机确认后PC 端拿到的新会话才能与现有的权限、风控、审计体系无缝衔接。8.4 异常时的降级与兜底任何强依赖的组件都可能抖动。扫码登录链路中扫码状态存储、消息推送、账号服务、风控服务任何一环不可用都可能影响用户登录。真实系统需要设计降级策略比如消息推送异常时自动回退到低频轮询扫码登录服务异常时允许用户切换到账号密码登录或其他方式保证主登录入口始终可用。面试时能主动谈到降级和兜底会让你的答案明显比只背流程的同学高出一个层次。九、面试回答框架把零散知识点串成一条线很多同学不是不懂单个知识点而是一被追问就乱。这里给一个可以直接套用的回答框架按照“一句话定义、三个阶段、两个关键问题、一份安全清单”来组织既不会漏点也能让面试官跟着你的节奏走。先给一句话定义二维码登录的本质是通过一张承载临时场景标识的二维码让已经登录的手机帮助未登录的电脑完成授权最终由服务端换发新会话的过程。二维码里不存敏感信息PC 与手机也不直连。再讲主流程PC 请求二维码服务端创建短期场景并生成二维码手机扫码后携带自己的登录态通知服务端用户在手机上确认服务端生成一次性临时票据PC 感知到状态变化用场景值换取正式会话。然后主动点出两个核心技术点一是 PC 端如何感知状态变化也就是普通轮询、长轮询、WebSocket、SSE 的选型二是服务端如何维护状态机保证状态单向流转、票据一次一用。最后补安全清单二维码短期有效、可刷新临时票据一次性兑换请求绑定来源设备手机确认页展示关键信息风控系统在关键节点介入。如果面试官继续追问某一点你就把对应章节展开。这样你的答案主次分明既不是流水账也不会在无关细节上迷路。十、Java 示例一个最小可运行的扫码登录状态机下面用一段 Java 代码把前面的状态机串起来。为了突出原理这里略去了数据库、Redis 和网络框架只用内存 Map 模拟存储但核心的状态约束与一次性兑换逻辑是完整的可以直接复制运行观察状态流转。import java.time.Duration; import java.time.Instant; import java.util.Map; import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; public class ScanLoginService { enum SceneState { WAITING_SCAN, SCANNED, CONFIRMED, COMPLETED } static class LoginScene { String sceneId; SceneState state SceneState.WAITING_SCAN; String mobileUser; Instant expireAt; String ticket; boolean ticketUsed; } private final MapString, LoginScene scenes new ConcurrentHashMap(); public String createScene() { LoginScene scene new LoginScene(); scene.sceneId UUID.randomUUID().toString(); scene.expireAt Instant.now().plus(Duration.ofMinutes(2)); scenes.put(scene.sceneId, scene); return https://login.example.com/scanConfirm?sceneId scene.sceneId; } public void scan(String sceneId, String mobileUser) { LoginScene scene requireValid(sceneId); if (scene.state ! SceneState.WAITING_SCAN) { throw new IllegalStateException(场景状态不允许扫码); } scene.state SceneState.SCANNED; scene.mobileUser mobileUser; } public String confirm(String sceneId) { LoginScene scene requireValid(sceneId); if (scene.state ! SceneState.SCANNED) { throw new IllegalStateException(场景未处于待确认状态); } scene.state SceneState.CONFIRMED; scene.ticket UUID.randomUUID().toString(); return scene.ticket; } public SceneState poll(String sceneId) { return requireValid(sceneId).state; } public String exchangeToken(String sceneId) { LoginScene scene requireValid(sceneId); if (scene.state ! SceneState.CONFIRMED || scene.ticketUsed) { throw new IllegalStateException(不允许兑换); } scene.ticketUsed true; scene.state SceneState.COMPLETED; return session- UUID.randomUUID(); } private LoginScene requireValid(String sceneId) { LoginScene scene scenes.get(sceneId); if (scene null || Instant.now().isAfter(scene.expireAt)) { throw new IllegalStateException(场景不存在或已过期); } return scene; } public static void main(String[] args) { ScanLoginService service new ScanLoginService(); String qrUrl service.createScene(); String sceneId qrUrl.substring(qrUrl.indexOf(sceneId) sceneId.length()); System.out.println(二维码内容: qrUrl); service.scan(sceneId, user_taobao); System.out.println(扫码后状态: service.poll(sceneId)); service.confirm(sceneId); System.out.println(确认后状态: service.poll(sceneId)); String token service.exchangeToken(sceneId); System.out.println(兑换登录态: token); } }这段代码体现了三个关键约束场景有明确过期时间状态只能按“等待扫码、已扫码、已确认、已完成”单向推进兑换成功后票据被标记为已使用重复兑换会被拒绝。真实工程中把 Map 换成 Redis再在exchangeToken里加入来源设备校验和原子操作就是一套可落地的核心骨架。写在最后回答这道题拼的是链路意识回到开头的问题“说说二维码登录的原理”。如果你只回答“生成二维码、扫一下、确认登录”那确实容易懵也容易被追问打穿。真正能拿下的回答是把二维码编码、临时场景、三方协同、状态通知、状态机、临时票据换会话、安全防护这几个点串成一条完整链路并且能随时在任何一个节点停下来展开。面试的本质不是证明你背过多少概念而是证明你能把一个看似简单的功能拆出足够多的分层、边界和异常场景。二维码登录恰恰是一道非常适合考察这种能力的题它足够常见人人都用过又足够有层次从客户端到服务端到安全每一层都值得深挖。把这篇文章的思路消化完下次再遇到这道题你应该可以从容地说一句这块我比较熟咱们从状态机讲起。