微信扫码登录是如何实现的?请描述一下技术方案
- 标签:后端、场景题、系统设计
🏛 业务架构师
微信扫码登录是 OAuth 思想在"App 扫码授权 PC"场景的变体,我按流程 → 状态机 → 安全设计讲,最后点出它为什么是值得投入的功能——扫码登录转化率显著高于账密登录(免输入、免记密码),同时是 App 拉活的重要入口。
完整流程(三端协作:PC 浏览器 / 服务端 / 手机 App)
- PC 请求二维码:PC 端调用服务端生成全局唯一二维码 ID(UUID),Redis 存储会话
qrcode:{id},状态"待扫描",TTL 约 2 分钟 - PC 轮询/长连接监听:PC 端轮询状态接口(或 WebSocket)等待状态变化
- App 扫码:App(已登录态)扫码解析出二维码 ID → 调用"已扫码"接口(带 App 的用户 token)→ 服务端把状态改为"已扫描待确认",返回授权确认页信息(站点名/Logo)
- App 确认授权:用户点确认 → 状态变"已确认",服务端生成一次性授权 code 关联该二维码会话
- PC 获取登录态:PC 轮询发现"已确认",拿到 code → 换取 PC 端登录态 token(session/JWT)→ 登录完成
状态机设计(核心数据模型)
待扫描 → 已扫描(待确认) → 已确认(登录成功)
↘ 过期(超时2分钟, 定时清理)
已扫描 → 用户拒绝 → 终态状态迁移全部原子操作(Redis Lua 或条件更新),每个状态有明确 TTL。
安全设计(必答)
- 二维码短时效 + 一次性:2 分钟过期、code 用后即焚——防截屏盗用
- 确认页防钓鱼:App 确认页必须展示申请登录的站点名称和 Logo——用户扫了钓鱼站点的码能看到异常,最后一道人工防线
- 扫码即绑定风险提示:异地扫码、频繁扫码触发风控(扫码确认也是账号被盗用的常见通道)
- 传输安全:二维码 ID 不含敏感信息(只是随机 ID),全链路 HTTPS
收尾:工程上这套"二维码 + 状态机 + 轮询"的模式是通用的——扫码支付、扫码入会、扫码配网全是同一骨架,值得沉淀成通用组件,一次建设多处复用,这才是架构师视角的投入产出。
🏗 系统架构师
微信扫码登录的本质是用手机上的登录态换取 PC 端登录态,核心是 OAuth2 授权码模式 + 扫码确认。我按完整时序讲。
1. 整体角色与流程
text
PC 浏览器 微信服务器(开放平台) 手机微信
│ 1.请求扫码登录 │ │
│──────────────────>│ │
│ 2.返回二维码 │ │
│ (二维码含唯一 │ │
│ uuid/scene_id) │ │
│<──────────────────│ │
│ 3.轮询/长连接查状态 │ 4.扫码(手机带微信登录态) │
│──────────────────>│<────────────────────────│
│ "待确认" │ 5.手机确认授权 │
│ │<────────────────────────│
│ 6.状态变"已确认" │ 7.生成临时code(换票) │
│<──────────────────│───code给手机侧完成闭环────│
│ 8.PC拿code │ │
│──────────────────>│ 后端用code+appSecret换 │
│ │ access_token+openid │
│ 9.换取用户信息,建 │ │
│ 本站会话,登录完成 │ │2. 关键技术点
- 二维码:内容是一个带
scene_id(全局唯一、5分钟有效、一次性)的 URL,手机微信识别后调开放平台接口"我扫了这个码"——码只是会话媒介,真正的状态都在服务端 - 状态查询(PC 侧感知扫码结果):
- 轮询:每 2s 查一次状态接口(简单,有延迟)
- 长连接/轮询降级:微信网页版用长连接(服务器 hold 住请求,状态变化立即推送)——扫码体验"秒变"的关键
- 状态机:
等待扫码 → 已扫码待确认 → 已确认(含code) → 过期
- 安全设计(重点):
- code 一次性 + 短时效(5 分钟),换 token 必须带 appSecret(服务端对服务端,浏览器接触不到)
- 确认动作在手机侧完成(手机已有微信登录态与用户生物/密码验证),PC 浏览器全程拿不到用户凭证,只拿 code
- 二维码过期机制(防"截图二维码异地扫"的钓鱼:确认页显示"微信网页版登录"目标站点名,用户可识别)
- redirect_uri 白名单、state 防 CSRF
- 本站会话建立:拿到 openid/unionid 后,关联本站账号(已绑定则直接登录;未绑定走绑定/注册流程),签发本站 JWT/session
3. 架构扩展:扫码状态存 Redis(scene_id → 状态,TTL 5 分钟);长连接网关可水平扩展(状态变更经 pub/sub 广播);量级上此模式同样适用于 App 扫码登录自家 PC 站,把"微信"换成"手机 App"即可——面试能点出这个迁移能力就是加分项。
💻 全栈开发专家
微信扫码登录本质是 OAuth2 授权码流程,我按"二维码生成 → 扫码授权 → 换取用户信息 → 建立本地会话"四步讲。
第一步:生成登录二维码
网站后端引导用户跳转微信开放平台授权页,前端展示微信返回的二维码:
text
https://open.weixin.qq.com/connect/qrconnect
?appid=APPID
&redirect_uri=https%3A%2F%2Fmyshop.com%2Fwx%2Fcallback
&response_type=code
&scope=snsapi_login
&state=abc123 # 防 CSRF,会话绑定随机串二维码状态(待扫码/已扫待确认/已确认/过期)由前端轮询本地接口感知,轮询键是微信侧的 uuid;内部系统自建"App 扫 PC 码"才需要 WebSocket + 二维码 token 状态机。
第二步:用户扫码确认,微信回调
用户微信里点确认后,微信 302 跳回 redirect_uri?code=CODE&state=abc123。后端先验 state(防 CSRF),code 一次性、5 分钟有效。
第三步:后端换取用户信息(服务端完成,不暴露 secret)
java
// ① code 换 access_token + openid
GET https://api.weixin.qq.com/sns/oauth2/access_token
?appid=APPID&secret=SECRET&code=CODE&grant_type=authorization_code
// ② 拉 userinfo(scope=snsapi_login 才有昵称头像)
GET https://api.weixin.qq.com/sns/userinfo?access_token=..&openid=..第四步:关联/注册本地账号,发自己系统的会话
java
User u = userMapper.selectByWxOpenid(openid);
if (u == null) { u = registerByWx(userinfo); } // openid/unionid 绑定
String token = issueJwt(u.getId()); // 自己签发会话
response.redirect("/login/success?token=" + token);关键工程点
- openid vs unionid:同一用户在多个公众号/小程序下 openid 不同,统一账号体系必须用 unionid(开放平台绑定主体后才有)。
- 首次登录的账号绑定:已注册用户(手机号)首次微信登录,走"绑定"流程而不是新建账号,否则一人两号。
- 安全:secret 只存服务端;code 换 token 必须服务端执行;回调 URL 白名单;access_token 缓存复用别每次刷新。
- 降级:微信接口偶发抖动要重试 + 熔断,登录主链路别被第三方拖死。
坑:扫码后长时间不确认 code 过期、二维码本身也要设 2 分钟过期并引导刷新,别让用户对着死码扫半天。