二维码登录的技术原理和代码如何实现?

wen java案例 52

本文目录导读:

二维码登录的技术原理和代码如何实现?

  1. 二维码登录技术原理
  2. 代码实现示例(简化版)
  3. 安全考虑
  4. 生产环境优化

二维码登录技术原理

二维码登录的核心原理是基于临时凭证轮询/推送机制的认证流程:

基本流程

  1. 生成二维码:服务端生成包含唯一标识符(UUID)的二维码,并存储该标识符与登录状态的映射
  2. 扫码:手机APP扫描二维码,获取其中的标识符
  3. 确认登录:手机端将用户令牌和标识符发送给服务端
  4. 验证轮询:PC端持续轮询服务端,检查该标识符对应的登录状态
  5. 登录成功:服务端返回登录凭证(如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>

安全考虑

  1. :使用随机生成的UUID,避免包含敏感信息
  2. 过期机制:设置合理的过期时间(通常5分钟)
  3. 一次性使用:扫码后状态立即变更,防止重复使用
  4. HTTPS传输:所有通信使用加密连接
  5. 防篡改:对关键数据进行签名验证
  6. 频率限制:对扫码和确认接口做频率限制,防止暴力攻击
  7. Token安全:使用JWT等安全的令牌格式,并设置合理的有效期

生产环境优化

  1. 使用Redis存储二维码状态,支持分布式部署
  2. 使用WebSocket代替轮询,提高实时性
  3. 添加设备信息和地理位置验证,增强安全性
  4. 实现二维码防重复扫描机制
  5. 添加日志记录异常监控

这个实现涵盖了二维码登录的核心流程,实际生产中还需要根据具体业务需求进行调整和优化。

抱歉,评论功能暂时关闭!