百度360必应搜狗淘宝本站头条
当前位置:网站首页 > IT技术 > 正文

SpringBoot2 二维码扫码登录原理与代码实战详解

wptr33 2025-04-09 21:27 8 浏览

SpringBoot2 二维码扫码登录原理与代码实战详解

首先,我需要回忆一下二维码登录的一般流程。通常,这种登录方式在微信、支付宝等应用中常见,用户用手机扫描网页上的二维码,然后手机端确认登录,网页端随即登录成功。

那原理部分,可能需要分步骤讲解。首先,网页生成二维码,里面包含一个唯一的标识,比如token或者UUID,并且这个标识需要与服务器建立某种关联,比如存储在Redis中,设置过期时间。然后,手机扫描二维码后,获取这个标识,并发送到服务器进行验证。服务器确认后,将用户信息与该标识绑定,网页端通过轮询或WebSocket等方式检测到状态变化,完成登录。

接下来是代码部分。用户可能希望看到具体的实现步骤,所以需要分模块讲解。比如,生成二维码的接口,处理扫码确认的接口,以及网页端的轮询接口。可能需要用到Spring Boot的Web模块,还有Redis作为存储,可能用到了WebSocket或者轮询机制。

需要注意的点:二维码的有效期管理,防止重复扫描,安全性问题比如token的随机性,防止伪造。还有,如何将用户信息与token关联,比如用户扫描后,手机端可能已经登录,如何获取用户信息绑定到token上,这里可能需要手机端的配合,但在代码实战中,可能需要模拟这个过程。

另外,在代码示例中,可能需要用到生成二维码的库,比如Google的ZXing库,或者其他的Java二维码生成工具。在Spring Boot中如何整合这些库,生成二维码图片返回给前端。

还有,前端部分如何展示二维码,并定时轮询后端接口,检查登录状态。前端可能使用JavaScript定时发送请求,或者使用WebSocket实时通信。

可能用户还关心异常处理,比如二维码过期后如何处理,重复扫描的提示,以及安全性措施,比如HTTPS的使用,token的加密传输等。

在写代码的时候,需要注意模块划分,比如Controller层处理请求,Service层处理业务逻辑,Redis的操作可能需要用到Spring Data Redis。还需要考虑接口的设计,比如生成二维码的接口返回什么数据,轮询接口的返回状态码设计等。

可能还需要考虑跨域问题,如果前端和后端分离的话,需要配置CORS。不过如果是同源的话,可能不需要。

总结一下,回答的结构应该是先讲原理,分步骤说明整个流程,然后给出Spring Boot 2的代码实现,包括依赖配置、生成二维码、处理扫描确认、轮询检查状态等部分,最后可能给出一些注意事项和扩展建议。

二维码扫码登录是一种常见的身份验证方式,广泛应用于Web和移动端。以下是Spring Boot 2实现二维码扫码登录的核心原理及代码实战详解。


一、扫码登录核心原理

  1. 流程概述
  2. 生成二维码:服务端生成唯一ID(如UUID)并与登录状态绑定,返回给前端生成二维码。
  3. 手机扫码:用户使用已登录的APP扫描二维码,将用户身份与二维码ID绑定。
  4. 状态轮询:网页端通过轮询或WebSocket实时检测登录状态,完成登录跳转。
  5. 关键技术点
  6. 唯一性标识:使用UUID或Token标识每个二维码。
  7. 状态存储:Redis存储二维码状态(未扫描/已确认/过期)。
  8. 实时通信:WebSocket或HTTP轮询实现状态同步。

二、Spring Boot 2 代码实战

1. 环境准备

依赖配置(pom.xml)



    org.springframework.boot
    spring-boot-starter-web



    org.springframework.boot
    spring-boot-starter-data-redis



    com.google.zxing
    core
    3.4.1


    com.google.zxing
    javase
    3.4.1

2. 生成二维码接口

Controller层

@RestController
@RequestMapping("/qrcode")
public class QrCodeController {

    @Autowired
    private StringRedisTemplate redisTemplate;

    @GetMapping("/generate")
    public ResponseEntity generateQrCode() throws Exception {
        String qrCodeId = UUID.randomUUID().toString();
        // 存储二维码状态:0-未扫描,1-已扫描待确认,2-已确认
        redisTemplate.opsForValue().set(qrCodeId, "0", 5, TimeUnit.MINUTES);
        
        // 生成二维码内容(例如包含qrCodeId)
        String content = "login:" + qrCodeId;
        ByteArrayOutputStream outputStream = QRCodeUtils.generateQrCode(content, 300, 300);
        return ResponseEntity.ok()
                .contentType(MediaType.IMAGE_PNG)
                .body(outputStream.toByteArray());
    }
}

工具类QRCodeUtils

public class QRCodeUtils {
    public static ByteArrayOutputStream generateQrCode(String content, int width, int height) throws WriterException, IOException {
        Map hints = new HashMap<>();
        hints.put(EncodeHintType.CHARACTER_SET, "UTF-8");
        BitMatrix matrix = new MultiFormatWriter().encode(content, BarcodeFormat.QR_CODE, width, height, hints);
        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
        MatrixToImageWriter.writeToStream(matrix, "PNG", outputStream);
        return outputStream;
    }
}

3. 手机端扫码确认

模拟APP扫码后调用确认接口

@PostMapping("/confirm")
public ResponseEntity confirmLogin(@RequestParam String qrCodeId, @RequestParam String userId) {
    String status = redisTemplate.opsForValue().get(qrCodeId);
    if ("0".equals(status)) {
        // 更新状态为已扫描待确认
        redisTemplate.opsForValue().set(qrCodeId, "1");
        // 绑定用户ID(实际场景需从APP登录态获取)
        redisTemplate.opsForValue().set(qrCodeId + ":user", userId);
        return ResponseEntity.ok("等待用户确认");
    }
    return ResponseEntity.badRequest().body("二维码已失效");
}

4. 网页端轮询状态

轮询接口

@GetMapping("/check")
public ResponseEntity<Map> checkStatus(@RequestParam String qrCodeId) {
    String status = redisTemplate.opsForValue().get(qrCodeId);
    Map result = new HashMap<>();
    if (status == null) {
        result.put("status", "expired");
    } else if ("1".equals(status)) {
        result.put("status", "confirmed");
        // 获取用户信息
        String userId = redisTemplate.opsForValue().get(qrCodeId + ":user");
        result.put("userId", userId);
        // 清除Redis数据
        redisTemplate.delete(qrCodeId);
        redisTemplate.delete(qrCodeId + ":user");
    } else {
        result.put("status", status); // 0-未扫描,1-已扫描
    }
    return ResponseEntity.ok(result);
}

5. 前端轮询逻辑(JavaScript示例)

let qrCodeId = "生成的二维码ID";
let poll = setInterval(() => {
    fetch(`/qrcode/check?qrCodeId=${qrCodeId}`)
        .then(response => response.json())
        .then(data => {
            if (data.status === 'confirmed') {
                clearInterval(poll);
                alert('登录成功,用户ID:' + data.userId);
            } else if (data.status === 'expired') {
                clearInterval(poll);
                alert('二维码已过期');
            }
        });
}, 2000); // 每2秒轮询一次

三、关键优化与注意事项

  1. 安全性
  2. Token加密:二维码内容使用JWT加密防止伪造。
  3. HTTPS:全程使用HTTPS防止中间人攻击。
  4. 性能优化
  5. WebSocket替代轮询:减少HTTP请求开销。
  6. Redis过期时间:合理设置二维码有效期(通常2-5分钟)。
  7. 用户体验
  8. 扫码提示:根据状态返回不同提示(如“请点击APP确认”)。
  9. 自动跳转:检测到登录成功后自动跳转至首页。

通过上述代码和原理分析,可实现完整的二维码扫码登录功能。实际项目中需结合具体业务调整用户信息绑定和安全性策略。

相关推荐

MYSQL 数据库触发器运用

#头条创作挑战赛#...

27.2k star,开源时序数据库里全球排名第一,InfluxDB何以做到?

一什么是时序数据库时序数据库,全称时间序列数据库(TimeSeriesDatabase,TSDB),用于存储大量基于时间的数据,时序数据(TimeSeriesData)指的是一系列基于时间的...

短链接API: service/timestamp

概览UNIX时间戳在SUBMAILAPI中有着非常重要的作用,它是大多数提交API请求时(几乎所有的POST请求)必要的签名参数。timestamp...

Flink架构及其工作原理(很详细)

原文链接:https://www.cnblogs.com/code2one/p/10123112.html关键词:Flink架构、面试杀手锏!更多大数据架构、实战经验,欢迎关注【大数据与机器学习】,...

influxdb基础那些事儿

InfluxDB是一个开源的时序数据库,使用GO语言开发,特别适合用于处理和分析资源监控数据这种时序相关数据。而InfluxDB自带的各种特殊函数如求标准差,随机取样数据,统计数据变化比等,使数据统计...

【大数据】Presto(Trino)SQL 语法进阶

一、概述Presto(Trino)是一个快速、分布式的SQL查询引擎,可以用于查询各种数据源,包括Hadoop、NoSQL、关系型数据库等。下面是Presto(Trino)SQL语法的概述:...

InfluxDB关键概念和常用术语介绍

在深入学习InfluxDB数据库之前,有必要先了解一些数据库的关键概念.writeformart(数据写入格式)向InfluxDB数据库中指定的measurement(表)中插入数据时遵循以下语法格...

用 Golang封装你的API
用 Golang封装你的API

每日分享最新,最流行的软件开发知识与最新行业趋势,希望大家能够一键三连,多多支持,跪求关注,点赞,留言。@头条创作挑战赛...

2025-05-21 16:54 wptr33

HIVE函数讲解之单行函数、聚合函数、炸裂函数、窗口函数

Hive是基于Hadoop的一个数据仓库工具,可以将结构化的数据文件映射为一张数据库表,并提供简单的SQL查询功能,可以将SQL语句转换为MapReduce任务进行运行。其优点是学习成本低,可以通过类...

go语言日期字符串和时间戳相互转换

Go语言中,获取时间戳用time.Now().Unix(),格式化时间用t.Format,解析时间用time.Parse。看实例代码:packagemainimport("f...

50道阿里巴巴MySql经典面试题(附答案)

1、MySQL中有哪几种锁?1、表级锁:开销小,加锁快;不会出现死锁;锁定力度大,发生锁冲突的概率最...

一文搞懂数据库索引原理

前言写数据库,我第一时间就想到了MySQL、Oracle、索引、存储过程、查询优化等等。不知道大家是不是跟我想得一样,我最想写的是索引,为啥呢?以下这个面试场景,不知道大家熟悉不熟悉:面试官:数据库有...

Hive内置函数使用详解:to_date()

to_date(stringtimestamp)返回时间戳字符串的日期部分。...

141_Power Query之获取钉钉审批流自动刷新Power BI报告

博客:www.jiaopengzi.com一、背景钉钉办公给很多企业带来了很多方便,比如审批流线上化,通用化。线上化填写后,数据自动获取又是一个硬伤了,虽然数据可以下载,但我们要自动刷新数据怎么办呢...

【法器篇】利用my2sql闪回误删数据

安装根据操作系统版本下载对应版本...