前端知库

扫码登录是怎样实现的?

PC 端向服务端申请一个带有效期的一次性二维码 ID,用轮询、长轮询或 WebSocket 等待状态变化;已登录的手机 App 扫码后带着自己的登录凭证确认,服务端把这个 ID 标记为已确认并给 PC 端签发新的会话。

参考答案

简答

打开网页版聊天软件,页面上出现一个二维码,用已经登录的手机 App 一扫、点“确认登录”,电脑上就进去了。这就是扫码登录。

背后是三方协作:PC 页面先向服务端要一个一次性的二维码 ID,有效期很短,把它画成二维码;手机 App 扫到 ID 后,带着手机自己的登录凭证告诉服务端“我要登录这个 ID”;服务端把这个 ID 的状态从“待扫码”改成“已扫码”,用户点确认后改成“已确认”,同时为 PC 端生成一份新的会话。

PC 页面怎么知道状态变了?可以定时轮询,也可以用长轮询或 WebSocket 让服务端主动推。状态通常有待扫码、已扫码待确认、已确认、已取消、已过期这几种,页面按状态切换提示。

安全上要注意三点:二维码 ID 要随机、短期有效、只能用一次;手机端确认页要显示登录设备和地点,防止别人把自己的二维码发给你扫;PC 端拿到的是服务端新签发的会话,不能直接复用手机的令牌。

整个流程是怎样走的

扫码登录没有统一的官方规范,各家产品实现细节不同。下面是常见的设计,思路和 OAuth 的设备授权流程(RFC 8628)很接近:一台不方便输密码的设备显示一个码,用户在另一台已登录的设备上确认。

  1. PC 页面请求“创建二维码”接口,服务端生成一个随机的 qrId,记录状态为“待扫码”,设好过期时间,返回给页面。
  2. 页面把 qrId(通常拼成一个 App 能识别的链接)画成二维码,然后开始等待状态。
  3. 手机 App 扫码,解析出 qrId,带着手机的登录凭证调用“扫码”接口。服务端校验凭证,把状态改成“已扫码”,并记下是哪个用户扫的。
  4. App 展示确认页,用户点“确认登录”,App 调用“确认”接口。服务端把状态改成“已确认”,为 PC 端生成新的会话。
  5. PC 页面得知“已确认”,拿到会话(比如服务端在这次响应里种下 Cookie),跳转进入。

PC 页面怎样知道状态变了

最简单的是轮询,每隔一两秒问一次状态。实现容易,代价是请求多、有最多一个间隔的延迟。

长轮询是服务端收到请求后先不回,等状态变了或者超时再回,页面收到响应马上发下一次。请求数少很多,延迟也低。

WebSocket 建一条长连接,服务端状态一变就推过来。实时性最好,但要维护连接和断线重连。扫码登录这种只等几十秒的场景,轮询或长轮询通常就够用。

不管用哪种,页面都要处理过期:服务端返回“已过期”时,页面显示“二维码已失效,点击刷新”,重新申请一个 ID。

RFC 8628 对设备授权流程的轮询规定可以借鉴:服务端返回 interval 表示两次轮询之间至少等多少秒,不返回时客户端默认等 5 秒;服务端可以回 slow_down 要求客户端把间隔再加 5 秒;expires_in 表示这个码多少秒后失效。

安全上要注意什么

先说二维码 ID 本身。它必须是不可猜测的随机值,不能用自增序号;有效期要短;确认一次以后立刻作废。否则别人猜到或截获 ID,就能抢先拿到会话。

再说“扫码钓鱼”。攻击者在自己电脑上打开登录页,把二维码截图发给你,说“扫一下领奖”。你一扫一确认,攻击者的电脑就登进了你的账号。RFC 8628 第 5.4 节专门讨论了这类远程钓鱼,建议在用户确认时告诉他“你正在授权一台设备”,并展示设备信息让他核对。所以确认页要写清“将在 Windows · Chrome · 北京 登录”,异地或陌生设备额外提醒。

最后说会话。PC 端的会话由服务端在确认时单独签发,和手机的凭证分开。这样用户可以在手机上单独踢掉某台 PC,手机的令牌也不会通过二维码流程暴露给 PC。

常见追问

为什么要分“已扫码”和“已确认”两步?

扫码只表示手机拿到了 ID,用户还没同意。分成两步,PC 页面能先显示“已扫码,请在手机上确认”,用户也有机会在确认页看到设备信息后拒绝。如果扫完直接登录,误扫或钓鱼就没有拦截的机会。

二维码里直接放登录令牌行不行?

不行。二维码会被截图、转发、被摄像头拍到,里面的内容要当成公开的。放进去的只能是一个短期、一次性、本身没有权限的 ID,真正的权限由服务端在用户确认后签发。

PC 页面放在后台很久,回来二维码过期了怎么办?

以服务端返回的状态为准。页面重新可见时立即查一次状态,过期了就提示刷新。不要只靠前端倒计时判断,因为后台标签页的定时器可能被浏览器限制,时间会不准。