图吧工具箱V2026.01:硬件检测与装机排查实战指南
2026/10/22 22:27:25
目标:用户在Web/PC 端看到二维码,用手机 App扫码确认后,Web/PC 端自动登录。
关键词:一次性二维码、短时会话、状态机、WebSocket/轮询、JWT/Session、风控与安全。
DISPLAYED:二维码已生成(Web 已拿到并展示)SCANNED:手机已扫码(已识别二维码)CONFIRMED:用户在手机确认CANCELLED:用户在手机取消EXPIRED:二维码过期CONSUMED:Web 已完成登录(票据已使用)DISPLAYED -> SCANNED -> CONFIRMED -> CONSUMED DISPLAYED -> SCANNED -> CANCELLED DISPLAYED -> EXPIRED SCANNED -> EXPIRED CONFIRMED -> EXPIRED (若 Web 长时间不换票据,ticket 可单独更短 TTL)SCANNED状态写入应幂等(SETNX 或 Lua)qrId允许重新订阅状态(WebSocket 重新连/轮询继续)不要直接放 userId / token。
只放一个随机、不可预测的会话标识:
qrId:128-bit 随机数(Base64URL / Hex)sig:服务端签名(防伪造二维码)ts:生成时间戳(辅助校验)示例(URL Scheme):
myapp://qr-login?qrId=AbCd...&ts=173...&sig=...或(HTTPS Deep Link / Universal Link):
https://auth.example.com/qr/scan?qrId=...&ts=...&sig=...sig = HMAC-SHA256(secret, qrId + "." + ts)abs(now - ts) <= 120s且HMAC一致qr:login:{qrId}→ Hash
status:DISPLAYED / SCANNED / CONFIRMED / CANCELLEDcreatedAtexpireAtwebClientId(可选:WebSocket 连接标识)deviceHint(可选:扫码设备信息,用于 Web 展示提示)confirmedUserId(CONFIRMED 后写入)ticket(CONFIRMED 后写入一次性票据)TTL:120s(二维码本体)
qr:ticket:{ticket}→ String(userId 或会话信息)
用 Lua 做状态推进,确保:
GET /api/qr-login/init→ 返回qrId、二维码图片/URL、过期时间ws://.../ws/qr-login?qrId=...GET /api/qr-login/poll?qrId=...SCANNED:UI 提示“已扫码,请在手机确认”CONFIRMED:拿到ticket,调用POST /api/qr-login/exchange换取登录态CANCELLED/EXPIRED:提示并刷新二维码qrId/ts/sigPOST /api/qr-login/scan(App 已登录,带 App Token)POST /api/qr-login/confirmPOST /api/qr-login/cancelGET/api/qr-login/init
响应:
{"qrId":"AbCdEf...","qrUrl":"myapp://qr-login?qrId=...&ts=...&sig=...","expireInSeconds":120}GET/api/qr-login/poll?qrId=...&sinceVersion=...
响应:
{"status":"SCANNED","deviceHint":"iPhone 15 · 上海","ticket":null,"expireInSeconds":80}POST/api/qr-login/scan
{"qrId":"...","ts":1730000000,"sig":"..."}返回(给 App 确认页展示):
{"status":"SCANNED","webInfo":{"ipCity":"New York","browser":"Chrome","os":"Windows","time":"2026-01-31T12:00:00Z"}}POST/api/qr-login/confirm
{"qrId":"..."}POST/api/qr-login/cancel
{"qrId":"..."}POST/api/qr-login/exchange
{"qrId":"...","ticket":"TICKET_..."}响应:Set-Cookie(Session/JWT Cookie) +{ "ok": true }
GET ws://auth.example.com/ws/qr-login?qrId=...
服务端验证:
qrId存在且未过期connId到qr:login:{qrId}.webClientId{"qrId":"...","status":"SCANNED","deviceHint":"..."}CONFIRMED推送可带ticket(建议只带 ticket,不带 userId):
{"qrId":"...","status":"CONFIRMED","ticket":"TICKET_..."}qrId -> wsSession映射,收到事件后推送GETDEL或 Lua 原子删除sig+ts防伪造二维码HttpOnly + Secure + SameSiteexchange校验Origin/Referer关键指标:
日志:
qrId贯穿全链路(trace tag)关键组件:
QrLoginService:状态机推进(Lua)RedisRepository:Key/TTL 封装WebSocketHandler:连接管理与推送AuthTokenService:签发 Web session/JWTRateLimit:限流(网关优先)Lua(伪代码简化):
-- KEYS[1] = qr:login:{qrId}-- ARGV[1] = targetStatuslocalst=redis.call('HGET',KEYS[1],'status')ifnotstthenreturn{err="NOT_FOUND"}endifst=='CANCELLED'orst=='CONSUMED'orst=='EXPIRED'thenreturn{ok=0,status=st}end-- ...按状态推进(略)return{ok=1}