扫描登录实现
扫码登录:核心思想与参与者
扫码登录的核心思想是 “授权转移”。它利用一个已经处于登录状态的设备(通常是手机App),来为另一个未登录的设备(通常是PC浏览器)进行身份认证和授权,从而免去在PC端输入账号密码的繁琐过程。
核心参与者 (Actors)
- 用户 (User): 操作的发起者。
- PC端/浏览器 (Client): 需要登录的设备,是授权的“接收方”。
- 移动端App (Authenticator): 已登录的设备,是授权的“发起方”。
- 后端服务 (Server): 整个流程的协调者和状态管理者。
技术实现流程详解
整个过程可以分解为几个关键阶段,其核心是围绕一个一次性、有时效性的唯一ID进行状态流转。
架构与交互流程图 (Sequence Diagram)
下面是整个扫码登录过程的详细交互时序图。
sequenceDiagram
participant PC端 as PC/Browser
participant 后端服务 as Server
participant 移动端App as Mobile App
participant 用户 as User
%% 1. 获取二维码
PC端->>后端服务: GET /api/auth/qrcode (请求登录二维码)
后端服务->>后端服务: 1. 生成唯一ID (UUID)
2. 存储状态(UNSCANNED)到Redis,并设置短时效(e.g., 120s)
key: qr:login:UUID, value: {status:"UNSCANNED"}
后端服务-->>PC端: 返回 {qrCodeId: UUID, url: "app://login?id=UUID"}
PC端->>PC端: 3. 根据返回的URL生成二维码并展示
PC端->>后端服务: 4. 开始轮询: GET /api/auth/status/{UUID}
loop 轮询检查状态
后端服务-->>PC端: 返回当前状态 {status:"UNSCANNED"}
end
%% 2. 用户扫码与确认
User->>移动端App: 打开App扫描二维码
移动端App->>移动端App: 5. 解析二维码, 获得UUID
移动端App->>后端服务: 6. POST /api/auth/scan (携带App的Token和UUID)
后端服务->>后端服务: 7. 校验App Token, 验证用户身份
8. 更新Redis状态: {status:"SCANNED", user:"..."}, 并重置过期时间
后端服务-->>移动端App: 返回成功, 要求用户确认
移动端App->>User: 9. 显示确认登录界面 (e.g., "确认在XX电脑登录?")
%% 3. PC端获取到状态变更
Note right of PC端: 轮询请求仍在继续
PC端->>后端服务: GET /api/auth/status/{UUID}
后端服务-->>PC端: 返回 {status:"SCANNED", avatar:"..."}
PC端->>PC端: 10. 更新UI, 显示用户头像, 提示"扫码成功,请在手机上确认"
%% 4. 用户确认,完成登录
User->>移动端App: 11. 点击"确认登录"
移动端App->>后端服务: 12. POST /api/auth/confirm (携带App Token和UUID)
后端服务->>后端服务: 13. 再次校验, 确认无误
14. 为PC端生成一个Session Token (e.g., JWT)
15. 更新Redis状态: {status:"CONFIRMED", token:"JWT_FOR_PC"}
后端服务-->>移动端App: 返回登录成功
%% 5. PC端最终获取Token
Note right of PC端: 轮询请求仍在继续
PC端->>后端服务: GET /api/auth/status/{UUID}
后端服务-->>PC端: 返回 {status:"CONFIRMED", token:"JWT_FOR_PC"}
PC端->>PC端: 16. 获取到Token, 停止轮询
17. 存储Token(localStorage/Cookie)
18. 跳转到用户主页, 登录成功!
后端服务->>后端服务: 19. (可选)清理Redis中的UUID数据
二维码状态机 (State Machine)
二维码的生命周期可以用一个简单的状态机来描述,后端服务需要严格管理这些状态。
stateDiagram-v2
direction LR
[*] --> UNSCANNED: 生成二维码
UNSCANNED --> SCANNED: 手机扫码
UNSCANNED --> EXPIRED: 超时
SCANNED --> CONFIRMED: 用户在手机上确认
SCANNED --> CANCELLED: 用户在手机上取消
SCANNED --> EXPIRED: 确认超时
CONFIRMED --> [*]: PC端获取Token后
CANCELLED --> [*]: 流程终止
EXPIRED --> [*]: 流程终止
关键技术点与实现细节
1. 二维码的生成与内容
- 唯一标识 (UUID): 核心是生成一个全局唯一、不可预测、一次性的ID。Java中的
UUID.randomUUID().toString()是绝佳选择。 - 二维码内容: 二维码中存储的内容不应是敏感信息,通常是一个自定义协议的URL,例如
myapp://login?qrcode_id=xxxx-xxxx-xxxx-xxxx。移动端App注册了这个协议,扫码后能直接唤起App并传递qrcode_id。
2. PC端与服务器的通信方式
PC端在展示二维码后,需要等待状态变更。有几种主流方式:
- 短轮询 (Short Polling): 实现简单,但延迟高、服务器压力大。不推荐。
- 长轮询 (Long Polling): 推荐方案。客户端发送请求后,如果服务器没有新状态,则将请求挂起(hold),直到有新状态或超时再返回。这大大减少了无效请求,降低了延迟。
- WebSocket: 最佳方案。建立一个持久的双向连接。服务器可以直接推送(push)状态变更给PC端,实时性最好,开销也最小。实现复杂度稍高。
3. 状态管理
- Redis: 扫码登录的状态是临时的、高频访问的,非常适合用Redis来存储。
- 数据结构: 可以使用
String或Hash。Hash更合适,可以将status,userInfo,pc_token等都存在一个key下。- Key:
qrcodelogin:{uuid} - Fields:
status,userId,avatar,token…
- Key:
- TTL (Time-To-Live): 必须为每个二维码ID设置过期时间(例如120秒),防止被恶意扫描或资源泄露。当用户扫码后,可以重置或延长这个过期时间。
- 数据结构: 可以使用
4. 安全性考量
- HTTPS: 所有通信链路必须使用HTTPS加密,防止中间人攻击。
- UUID的不可预测性: 保证攻击者无法伪造或猜测出有效的二维码ID。
- 一次性使用: 一个二维码ID在被成功使用一次后,必须立即失效。
- 移动端Token: 移动端App在与后端交互时,必须携带自己的有效Token,以证明其操作的合法性。
- 风险提示: 在手机确认界面,应明确显示登录的设备类型、地理位置(通过IP估算)、时间等信息,让用户清晰地知道自己在授权什么。
后端伪代码示例 (Java/Spring Boot)
1 |
|
总结
扫码登录是一个典型的分布式、多端协作的业务场景。其实现优雅与否,关键在于:
- 清晰的状态管理:定义好二维码的生命周期,并用可靠的工具(如Redis)进行管理。
- 高效的通信机制:选择长轮询或WebSocket来避免资源浪费,提升用户体验。
- 严密的安全性设计:在每个环节都考虑安全风险,确保授权过程不被劫持或滥用。