本文目录导读:

二维码登录技术原理
二维码登录的核心原理是基于临时凭证和轮询/推送机制的认证流程:
基本流程
- 生成二维码:服务端生成包含唯一标识符(UUID)的二维码,并存储该标识符与登录状态的映射
- 扫码:手机APP扫描二维码,获取其中的标识符
- 确认登录:手机端将用户令牌和标识符发送给服务端
- 验证轮询:PC端持续轮询服务端,检查该标识符对应的登录状态
- 登录成功:服务端返回登录凭证(如JWT Token)
代码实现示例(简化版)
后端实现(Node.js + Express)
const express = require('express');
const crypto = require('crypto');
const QRCode = require('qrcode');
const app = express();
// 存储二维码状态(生产环境应使用Redis)
const qrCodeStore = new Map();
// 1. 生成二维码接口
app.get('/api/qrcode/generate', async (req, res) => {
const qrId = crypto.randomUUID();
// 存储二维码状态
qrCodeStore.set(qrId, {
status: 'pending', // pending, scanned, confirmed, expired
createdAt: Date.now(),
userId: null
});
// 生成二维码(这里使用临时令牌作为二维码内容)
const qrContent = JSON.stringify({
type: 'login',
qrId: qrId,
timestamp: Date.now()
});
// 生成二维码图片(Base64格式)
const qrCodeDataUrl = await QRCode.toDataURL(qrContent);
res.json({
success: true,
qrId: qrId,
qrCode: qrCodeDataUrl,
expiresIn: 300 // 5分钟过期
});
});
// 2. 扫码确认接口(手机端调用)
app.post('/api/qrcode/scan', (req, res) => {
const { qrId, userId } = req.body;
const qrData = qrCodeStore.get(qrId);
if (!qrData) {
return res.status(404).json({ success: false, message: '二维码已过期' });
}
if (qrData.status !== 'pending') {
return res.status(400).json({ success: false, message: '二维码已被扫描' });
}
// 更新状态为已扫描(手机APP已识别)
qrData.status = 'scanned';
qrData.userId = userId;
qrCodeStore.set(qrId, qrData);
res.json({ success: true, message: '扫码成功' });
});
// 3. 确认登录接口(手机端确认登录后调用)
app.post('/api/qrcode/confirm', (req, res) => {
const { qrId, token } = req.body;
const qrData = qrCodeStore.get(qrId);
if (!qrData || qrData.status !== 'scanned') {
return res.status(400).json({ success: false });
}
// 生成JWT Token或其他登录凭证
const loginToken = generateJWT(qrData.userId);
// 更新状态为已确认
qrData.status = 'confirmed';
qrData.loginToken = loginToken;
qrCodeStore.set(qrId, qrData);
res.json({ success: true });
});
// 4. 轮询接口(PC端调用,检查登录状态)
app.get('/api/qrcode/status/:qrId', (req, res) => {
const { qrId } = req.params;
const qrData = qrCodeStore.get(qrId);
if (!qrData) {
return res.json({ status: 'expired', message: '二维码已过期' });
}
// 检查是否过期(5分钟)
if (Date.now() - qrData.createdAt > 300000) {
qrData.status = 'expired';
qrCodeStore.set(qrId, qrData);
return res.json({ status: 'expired', message: '二维码已过期' });
}
// 根据不同状态返回
const response = {
status: qrData.status,
message: ''
};
switch(qrData.status) {
case 'pending':
response.message = '等待扫描';
break;
case 'scanned':
response.message = '已扫描,等待确认';
break;
case 'confirmed':
response.message = '登录成功';
response.token = qrData.loginToken;
break;
}
res.json(response);
});
// 辅助函数:生成JWT
function generateJWT(userId) {
// 实际使用jsonwebtoken库
return `jwt_${userId}_${Date.now()}`;
}
app.listen(3000, () => {
console.log('二维码登录服务运行在端口3000');
});
前端实现(Vue.js示例)
<template>
<div class="qr-login">
<div v-if="loading">
<img :src="qrCodeUrl" alt="二维码" />
<p>请使用手机APP扫描二维码</p>
<p>状态:{{ statusText }}</p>
</div>
<div v-else-if="error">
<p>二维码加载失败:{{ error }}</p>
<button @click="generateQR">重新获取</button>
</div>
<div v-else-if="loggedIn">
<p>登录成功!欢迎回来</p>
<p>Token: {{ token }}</p>
</div>
</div>
</template>
<script>
export default {
data() {
return {
qrId: '',
qrCodeUrl: '',
status: 'pending',
token: '',
loading: false,
error: '',
pollTimer: null
}
},
computed: {
statusText() {
const map = {
'pending': '等待扫描',
'scanned': '已扫描,请在手机上确认',
'confirmed': '登录成功',
'expired': '二维码已过期'
}
return map[this.status] || '未知状态'
}
},
methods: {
async generateQR() {
this.loading = true
this.error = ''
try {
const response = await fetch('/api/qrcode/generate')
const data = await response.json()
this.qrId = data.qrId
this.qrCodeUrl = data.qrCode
this.status = 'pending'
// 开始轮询
this.startPolling()
} catch (err) {
this.error = err.message
} finally {
this.loading = false
}
},
startPolling() {
this.stopPolling()
this.pollTimer = setInterval(async () => {
try {
const response = await fetch(`/api/qrcode/status/${this.qrId}`)
const data = await response.json()
this.status = data.status
if (data.status === 'confirmed') {
this.token = data.token
this.stopPolling()
} else if (data.status === 'expired') {
this.stopPolling()
this.error = '二维码已过期,请刷新页面重新获取'
}
} catch (err) {
console.error('轮询失败:', err)
}
}, 2000) // 每2秒轮询一次
},
stopPolling() {
if (this.pollTimer) {
clearInterval(this.pollTimer)
this.pollTimer = null
}
}
},
mounted() {
this.generateQR()
},
beforeDestroy() {
this.stopPolling()
}
}
</script>
安全考虑
- :使用随机生成的UUID,避免包含敏感信息
- 过期机制:设置合理的过期时间(通常5分钟)
- 一次性使用:扫码后状态立即变更,防止重复使用
- HTTPS传输:所有通信使用加密连接
- 防篡改:对关键数据进行签名验证
- 频率限制:对扫码和确认接口做频率限制,防止暴力攻击
- Token安全:使用JWT等安全的令牌格式,并设置合理的有效期
生产环境优化
- 使用Redis存储二维码状态,支持分布式部署
- 使用WebSocket代替轮询,提高实时性
- 添加设备信息和地理位置验证,增强安全性
- 实现二维码防重复扫描机制
- 添加日志记录和异常监控
这个实现涵盖了二维码登录的核心流程,实际生产中还需要根据具体业务需求进行调整和优化。
版权声明:NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
足球直播
足球直播
足球直播
篮球直播
篮球直播
篮球直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
亚冠直播
亚冠直播
亚冠直播
欧冠直播
欧冠直播
欧冠直播
英超直播
英超直播
英超直播
西甲直播
西甲直播
西甲直播
中超直播
中超直播
中超直播
足球直播
足球直播
足球直播
篮球直播
篮球直播
篮球直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
亚冠直播
亚冠直播
亚冠直播
欧冠直播
欧冠直播
欧冠直播
英超直播
英超直播
英超直播
西甲直播
西甲直播
西甲直播
中超直播
中超直播
中超直播
足球直播
足球直播
足球直播
篮球直播
篮球直播
篮球直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
亚冠直播
亚冠直播
亚冠直播
欧冠直播
欧冠直播
欧冠直播
英超直播
英超直播
英超直播
西甲直播
西甲直播
西甲直播
中超直播
中超直播
中超直播
足球直播
足球直播
足球直播
篮球直播
篮球直播
篮球直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
亚冠直播
亚冠直播
亚冠直播
欧冠直播
欧冠直播
欧冠直播
英超直播
英超直播
英超直播
西甲直播
西甲直播
西甲直播
中超直播
中超直播
中超直播
曼联直播
曼联直播
曼联直播
日职联直播
日职联直播
日职联直播
澳超直播
澳超直播
澳超直播
阿森纳直播
阿森纳直播
阿森纳直播
足球直播
足球直播
足球直播
NBA直播
NBA直播
NBA直播
曼联直播
曼联直播
曼联直播
日职联直播
日职联直播
日职联直播
澳超直播
澳超直播
澳超直播
阿森纳直播
阿森纳直播
阿森纳直播
足球直播
足球直播
足球直播
NBA直播
NBA直播
NBA直播
曼联直播
曼联直播
曼联直播
日职联直播
日职联直播
日职联直播
澳超直播
澳超直播
澳超直播
阿森纳直播
阿森纳直播
阿森纳直播
足球直播
足球直播
足球直播
NBA直播
NBA直播
NBA直播
曼联直播
曼联直播
曼联直播
日职联直播
日职联直播
日职联直播
澳超直播
澳超直播
澳超直播
阿森纳直播
阿森纳直播
阿森纳直播
足球直播
足球直播
足球直播
NBA直播
NBA直播
NBA直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
足球直播
足球直播
足球直播
足球直播
足球直播
英超直播
英超直播
英超直播
英超直播
英超直播
篮球直播
篮球直播
篮球直播
篮球直播
篮球直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
中超直播
中超直播
中超直播
中超直播
中超直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
足球直播
足球直播
足球直播
足球直播
足球直播
英超直播
英超直播
英超直播
英超直播
英超直播
篮球直播
篮球直播
篮球直播
篮球直播
篮球直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
中超直播
中超直播
中超直播
中超直播
中超直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播