微信小程序扫码登录:从零实现一个完整的登录系统
前言
微信小程序扫码登录是当下 Web 应用中非常常见的登录方式。用户在浏览器上看到一个二维码,用微信扫一扫,小程序弹出确认页面,点击确认后 Web 端自动完成登录——整个体验流畅且安全。
本文将基于一个基于 Hono 框架的实际项目,从架构设计和实现逻辑的角度,完整梳理扫码登录的核心流程与关键技术决策。
整体架构
项目采用经典的分层架构,基于 Hono 框架构建后端 API 服务:
- 配置层:通过 Zod Schema 校验环境变量,确保配置的类型安全
- 类型层:定义登录状态机、错误码、请求/响应接口等核心类型
- 服务层:封装会话管理、JWT 签发、微信 API 交互、用户管理等业务逻辑
- 路由层:定义 API 端点,串联服务层完成请求处理
- 中间件层:统一响应格式,将所有 API 响应包装为
{ code, msg, data }结构
项目还集成了 hono-openapi 和 Scalar,自动生成 OpenAPI 文档并提供交互式 API 文档界面,方便前端联调和测试。
核心流程:一个五态状态机
整个扫码登录的本质是一个有限状态机。每个登录会话(Session)都有唯 一的状态,状态之间只能按预定义的路径转移。
状态定义
登录会话定义了五种状态:
| 状态 | 含义 |
|---|---|
pending | 会话已创建,等待用户扫码 |
scanned | 用户已扫码,等待确认 |
confirmed | 用户已确认,登录成功 |
expired | 会话超时失效 |
cancelled | 用户主动取消 |
合法状态转移
状态之间的转移被严格约束,非法转移会被拒绝:
pending --> scanned / expired / cancelled
scanned --> confirmed / expired / cancelled
confirmed / expired / cancelled --> 终态,不可再转移
这个状态机设计保证了登录流程的安全性。例如,一个已经确认登录的会话不能被重复确认,防止重放攻击;一个已取消的会话也不能被恢复。
完整时序
整个登录流程涉及三个参与方:Web 浏览器、后端服务、微信小程序。
┌──────────┐ ┌──────────┐ ┌──────────┐
│ Web端 │ │ 后端服务 │ │ 小程序 │
└────┬─────┘ └────┬─────┘ └────┬─────┘
│ │ │
│ POST /api/qrcode │ │
│─────────────────────>│ │
│ │── 创建Session(pending)│
│ │── 调用微信API生成小程序码│
│ 返回sessionId+二维码 │ │
│<─────────────────────│ │
│ │ │
│ 开始轮询GET /api/status/:sessionId │
│─────────────────────>│ │
│ 返回status=pending │ │
│<─────────────────────│ │
│ │ │
│ │ 用户扫码,打开小程序 │
│ │ 小程序获取scene参数 │
│ │ 调用wx.login获取code │
│ │ │
│ │ POST /api/scan │
│ │ {sessionId, code} │
│ │<─────────────────────│
│ │── code2Session换取openid│
│ │── 更新Session(scanned) │
│ │ │
│ GET /api/status │ │
│─────────────────────>│ │
│ 返回status=scanned │ │
│<─────────────────────│ │
│ │ │
│ │ POST /api/confirm │
│ │ {sessionId, code} │
│ │<─────────────────────│
│ │── 再次code2Session │
│ │── 验证openid一致性 │
│ │── 签发JWT Token │
│ │── 更新Session(confirmed)│
│ │ │
│ GET /api/status │ │
│─────────────────────>│ │
│ 返回status=confirmed│ │
│ + userInfo + token │ │
│<─────────────────────│ │
│ │ │
│ 登录完成 │ │
└──────────────────────┘ │
关键设计细节
1. 二维码生成:小程序码而非普通二维码
项目使用微信的 getwxacodeunlimit 接口生成小程序码,而非普通的二维码图片。小程序码的优势在于:
- 用户扫码后直接打开小程序的指定页面
- 可以通过
scene参数传递会话 ID - 支持指定小程序版本(开发版/体验版/正式版),方便开发调试
后端在生成小程序码时,会将 sessionId 去除连字符后作为 scene 参数传递给微信 API。小程序打开后会从 scene 参数中解析出 sessionId,从而建立起与 Web 端登录会话的关联。