扫码登录实现方案
实现 PC 端扫描二维码登录的完整流程
问题
如何实现扫描二维码登录 PC 网站?
解答
登录原理
二维码登录是一种身份认证方式,需要完成两个核心任务:
- 告诉系统我是谁
- 向系统证明我是谁
完整流程
第一步:生成二维码
PC 端向服务端请求生成登录二维码:
// PC 端请求生成二维码
async function generateQRCode() {
const deviceInfo = getDeviceInfo(); // 获取设备信息
const response = await fetch('/api/qrcode/generate', {
method: 'POST',
body: JSON.stringify({ deviceInfo })
});
const { qrcodeId } = await response.json();
// 生成二维码图片
displayQRCode(qrcodeId);
// 开始轮询二维码状态
pollQRCodeStatus(qrcodeId);
}
// 轮询二维码状态
function pollQRCodeStatus(qrcodeId) {
const timer = setInterval(async () => {
const response = await fetch(`/api/qrcode/status?id=${qrcodeId}`);
const { status, token } = await response.json();
if (status === 'scanned') {
updateUI('已扫描,请在手机端确认');
} else if (status === 'confirmed') {
clearInterval(timer);
loginSuccess(token);
}
}, 1000);
}
服务端生成二维码 ID 并绑定设备信息:
// 服务端生成二维码
app.post('/api/qrcode/generate', (req, res) => {
const { deviceInfo } = req.body;
const qrcodeId = generateUUID();
// 存储二维码信息(Redis 示例)
redis.setex(`qrcode:${qrcodeId}`, 300, JSON.stringify({
deviceInfo,
status: 'waiting',
createdAt: Date.now()
}));
res.json({ qrcodeId });
});
第二步:扫描二维码
手机端扫描二维码并发送身份信息:
// 手机端扫描二维码
async function scanQRCode(qrcodeId) {
const userToken = getLocalToken(); // 手机端已登录的 token
const response = await fetch('/api/qrcode/scan', {
method: 'POST',
headers: {
'Authorization': `Bearer ${userToken}`
},
body: JSON.stringify({ qrcodeId })
});
const { tempToken } = await response.json();
// 显示确认界面
showConfirmDialog(tempToken);
}
服务端处理扫描请求:
// 服务端处理扫描
app.post('/api/qrcode/scan', authenticate, (req, res) => {
const { qrcodeId } = req.body;
const userId = req.user.id;
// 更新二维码状态
const qrcodeData = redis.get(`qrcode:${qrcodeId}`);
qrcodeData.status = 'scanned';
qrcodeData.userId = userId;
// 生成临时 token
const tempToken = generateTempToken(qrcodeId, userId);
redis.setex(`qrcode:${qrcodeId}`, 300, JSON.stringify(qrcodeData));
res.json({ tempToken });
});
第三步:确认登录
手机端确认登录:
// 手机端确认登录
async function confirmLogin(tempToken) {
await fetch('/api/qrcode/confirm', {
method: 'POST',
body: JSON.stringify({ tempToken })
});
showSuccess('登录成功');
}
服务端生成 PC 端登录 token:
// 服务端处理确认
app.post('/api/qrcode/confirm', (req, res) => {
const { tempToken } = req.body;
const { qrcodeId, userId } = verifyTempToken(tempToken);
// 获取设备信息
const qrcodeData = redis.get(`qrcode:${qrcodeId}`);
// 生成 PC 端登录 token
const loginToken = generateLoginToken(userId, qrcodeData.deviceInfo);
// 更新状态
qrcodeData.status = 'confirmed';
qrcodeData.token = loginToken;
redis.setex(`qrcode:${qrcodeId}`, 60, JSON.stringify(qrcodeData));
res.json({ success: true });
});
使用 WebSocket 优化
替代轮询方案,使用 WebSocket 实时通知:
// PC 端建立 WebSocket 连接
const ws = new WebSocket(`ws://api.example.com/qrcode/${qrcodeId}`);
ws.onmessage = (event) => {
const { status, token } = JSON.parse(event.data);
if (status === 'scanned') {
updateUI('已扫描,请在手机端确认');
} else if (status === 'confirmed') {
loginSuccess(token);
}
};
关键点
- 二维码中包含唯一 ID,服务端通过 ID 绑定设备信息和用户身份
- 手机端必须是已登录状态,扫码时携带身份凭证
- 使用临时 token 作为扫描和确认之间的凭证,提高安全性
- PC 端通过轮询或 WebSocket 实时获取二维码状态变化
- 二维码应设置过期时间(如 5 分钟),避免安全风险
目录