扫码登录的简单实现

扫码登录的简单实现前言本文将介绍基于 SpringBoot Vue Android 实现的扫码登录 demo 的总体思路 完整代码已上传到 GitHub Web 端体验地址 http 47 116 72 33 只剩一个月有效期 apk 下载地址 https github com zhangjiwei12 qrscan releases tag 0 0 1 用户名 非空即可 密码 效果见文末 整体实现如有不妥之处 欢迎交流讨论 实现部分参考二维码扫码登录是什么原理 项目简介后端 SpringBoo

前言

本文将介绍基于SpringBoot + Vue + Android实现的扫码登录demo的总体思路,完整代码已上传到GitHub。Web端体验地址:http://42.192.64.144/qr,apk下载地址:qrscan。用户名:非空即可,密码:,效果见文末,整体实现如有不妥之处,欢迎交流讨论,实现部分参考二维码扫码登录是什么原理。

项目简介

后端:SpringBootRedis
前端:VueVue RouterVueXAxiosvue-qrElemntUI
安卓:ZXingXUIYHttp




实现思路

总体的扫码登录和OAuth2.0的验证逻辑相似,如下所示:

image-20210921205657426

用户选择扫码登录可以看作是A:前端发授权请求,等待app扫码。
用户使用app进行扫码可以看作是B:扫码进行授权,返回一个临时Token供二次认证。
用户在app进行确认登录可以看作是C:进行登录确认,授权用户在Web端登录。
后端在用户确认登录后返回一个正式Token即可看作是步骤D
后续前端根据正式Token访问后台接口,正式在Web端进行操作即可看作是EF








二次认证的原因

之所以在用户扫码之后还需要进行再一次的确认登录,而不是直接就登录的原因,则是为了用户安全考虑,避免用户扫了其他人需要登录的二维码,在未经确认就直接登录了,导致他人可能会在我们不知道的情况下访问我们的信息。

实现步骤

  1. 用户访问网页端,选择扫码登录

    用户在选择扫码登录时,会向后端发送一个二维码的生成请求,后端生成UUID,并保存到Redis(固定有效时间),状态设置为UNUSED(未使用)状态,如果Redis缓存过期,则为EXPIRE(过期)状态,前端根据后端返回的内容生成二维码,并设置一个定时器,每隔一段时间根据二维码的内容中的UUID,向后端发送请求,获取二维码的状态,更新界面展示的内容。

    生成二维码后端接口:

    / * 生成二维码内容 * * @return 结果 */ @GetMapping("/generate") public BaseResult generate() { 
          String code = IdUtil.simpleUUID(); redisCache.setCacheObject(code, CodeUtils.getUnusedCodeInfo(), DEFAULT_QR_EXPIRE_SECONDS, TimeUnit.SECONDS); return BaseResult.success(GENERATE_SUCCESS, code); } 

    前端获取内容,生成二维码:

    getToken() { 
          this.codeStatus = 'EMPTY' this.tip = '正在获取登录码,请稍等' // 有效时间 60 秒 this.effectiveSeconds = 60 clearInterval(this.timer) request({ 
          method: 'get', url: '/code/generate' }).then((response) => { 
          // 请求成功, 设置二维码内容, 并更新相关信息 this.code = `${ 
           HOST}/code/scan?code=${ 
           response.data}` this.codeStatus = 'UNUSED' this.tip = '请使用手机扫码登录' this.timer = setInterval(this.getTokenInfo, 2000) }).catch(() => { 
          this.getToken() }) } 

    后端返回二维码状态信息的接口:

    / * 获取二维码状态信息 * * @param code 二维码 * @return 结果 */ @GetMapping("/info") public BaseResult info(String code) { 
          CodeVO codeVO = redisCache.getCacheObject(code); if (codeVO == null) { 
          return BaseResult.success(INVALID_CODE, StringUtils.EMPTY); } return BaseResult.success(GET_SUCCESS, codeVO); } 

    前端轮询获取二维码状态:

    getTokenInfo() { 
          this.effectiveSeconds-- // 二维码过期 if (this.effectiveSeconds <= 0) { 
          this.codeStatus = 'EXPIRE' this.tip = '二维码已过期,请刷新' return } // 轮询查询二维码状态 request({ 
          method: 'get', url: '/code/info', params: { 
          code: this.code.substr(this.code.indexOf('=') + 1) } }).then(response => { 
          const codeVO = response.data // 二维码过期 if (!codeVO || !codeVO.codeStatus) { 
          this.codeStatus = 'EXPIRE' this.tip = '二维码已过期,请刷新' return } // 二维码状态为为正在登录 if (codeVO.codeStatus === 'CONFIRMING') { 
          this.username = codeVO.username this.avatar = codeVO.avatar this.codeStatus = 'CONFIRMING' this.tip = '扫码成功,请在手机上确认' return } // 二维码状态为确认登录 if (codeVO.codeStatus === 'CONFIRMED') { 
          clearInterval(this.timer) const token = codeVO.token store.commit('setToken', token) this.$router.push('/home') Message.success('登录成功') return } }) } 
  2. 使用手机扫码,二维码状态改变

    当用户使用手机扫码时(已登录并且为正确的app,否则扫码会跳转到自定义的宣传页),会更新二维码的状态为CONFIRMING(待确认)状态,并在Redis缓存中新增用户名及头像信息的保存供前端使用展示,此外还会返回用户的登录信息(登录地址、浏览器、操作系统)给app展示,同时生成一个临时Tokenapp(固定有效时间)。

    用户扫码时的后台处理:

    / * 处理未使用状态的二维码 * * @param code 二维码 * @param token token * @return 结果 */ private BaseResult handleUnusedQr(String code, String token) { 
          // 校验 app 端访问传递的 token boolean isLegal = JwtUtils.verify(token); if (!isLegal) { 
          return BaseResult.error(AUTHENTICATION_FAILED); } // 保存用户名、头像信息, 供前端展示 String username = JwtUtils.getUsername(token); CodeVO codeVO = CodeUtils.getConfirmingCodeInfo(username, DEFAULT_AVATAR_URL); redisCache.setCacheObject(code, codeVO, DEFAULT_QR_EXPIRE_SECONDS, TimeUnit.SECONDS); // 返回登录地址、浏览器、操作系统以及一个临时 token 给 app String address = HttpUtils.getRealAddressByIp(); String browser = HttpUtils.getBrowserName(); String os = HttpUtils.getOsName(); String tmpToken = JwtUtils.sign(username); // 将临时 token 作为键, 用户名为内容存储在 redis 中 redisCache.setCacheObject(tmpToken, username, DEFAULT_TEMP_TOKEN_EXPIRE_MINUTES, TimeUnit.MINUTES); LoginInfoVO loginInfoVO = new LoginInfoVO(address, browser, os, tmpToken); return BaseResult.success(SCAN_SUCCESS, loginInfoVO); } 
  3. 手机确认登录

    当用户在app中点击确认登录时,就会携带生成的临时Token发送更新状态的请求,二维码的状态会被更新为CONFIRMED(已确认登录)状态,同时后端会生成一个正式Token保存在Redis中,前端在轮询更新状态时获取这个Token,然后使用这个Token进行登录。

    后端处理确认登录的代码:

    / * 处理未待确认状态的二维码 * * @param code 二维码 * @param token token * @return 结果 */ private BaseResult handleConfirmingQr(String code, String token) { 
          // 使用临时 token 获取用户名, 并从 redis 中删除临时 token String username = redisCache.getCacheObject(token); if (StringUtils.isBlank(username)) { 
          return BaseResult.error(AUTHENTICATION_FAILED); } redisCache.deleteObject(token); // 根据用户名生成正式 token并保存在 redis 中供前端使用 String formalToken = JwtUtils.sign(username); CodeVO codeVO = CodeUtils.getConfirmedCodeInfo(username, DEFAULT_AVATAR_URL, formalToken); redisCache.setCacheObject(code, codeVO, DEFAULT_QR_EXPIRE_SECONDS, TimeUnit.SECONDS); return BaseResult.success(CONFIRM_SUCCESS); } 

效果演示

在这里插入图片描述
在这里插入图片描述

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/203924.html原文链接:https://javaforall.net

(0)
上一篇 2026年3月19日 下午9:17
下一篇 2026年3月19日 下午9:17


相关推荐

  • SpringMVC整合SwaggerUI

    SpringMVC整合SwaggerUISpringMVC 整合 SwaggerUI 文章目录 SpringMVC 整合 SwaggerUI 概念介绍相关资源下载编写整合代码 index html 文件修改 maven 中引入依赖 springmvc 配置文件中配置资源路径编写 swagger 配置文件效果展示整合时可能出现的异常问题分析问题解决总结参考资料网上介绍 Swagger 整合的文章很多 但都是东拼西凑 抄来抄去 讲不清楚重点 本文的目的就是希望那些从来没

    2025年11月28日
    5
  • 通俗解释hash碰撞是什么以及如何解决

    通俗解释hash碰撞是什么以及如何解决Hash如何存数据hash表的本质其实就是数组,hash表中通常存放的是键值对Entry。如下图:这里的学号是个key,哈希表就是根据key值来通过哈希函数计算得到一个值,这个值就是下标值,用来确定这个Entry要存放在哈希表中哪个位置。Hash碰撞hash碰撞指的是,两个不同的值(比如张三、李四的学号)经过hash计算后,得到的hash值相同,后来的李四要放到原来的张三的位置,但是数组的位置已经被张三占了,导致冲突。解决方法hash碰撞的解决方式是开放寻址法和拉..

    2022年6月16日
    29
  • shell高级技巧:提取vcf文件中一个contig

    shell高级技巧:提取vcf文件中一个contig这是一个很小众的需求 大部分变异检测都是基于组装质量比较高的基因组 而不是那种初步拼接的 contig 由于初步拼接的参考序列通常会有成千上万个 contig 序列 也就导致在 VCF 的头文件的 contig ID xxx length xxx 部分会有成千上万个 contig 将这个文件加载到 IGV 时 IGV 会去解析 VCF 这将会是非常缓慢的过程 最好的策略就是只提取其 ID xxx length xxx

    2025年6月2日
    7
  • java 定时器 传参数_Java定时器

    java 定时器 传参数_Java定时器Java 定时器在应用开发中 经常需要一些周期性的操作 比如每 5 分钟执行某一操作等 在 WEB 项目中可能需要每隔一段时间自动生成静态页 自动检测是否有新邮件 定时自动备份文件等操作 这些都可以通过定时器 Timer 来解决 这里仅提供定时器的部分方法和说明 静态页生成等操作读者可以通过相关方法自行解决 百度空间贴代码总是很让人头疼 排好的代码拿到这里就出现混乱 1 Timer schedule T

    2026年3月19日
    1
  • usb转rs485测试软件,usb转rs485驱动程序

    usb转rs485测试软件,usb转rs485驱动程序usb转rs485线必须安装usb转rs485驱动程序才可以正常使用,而本次发布的这个usb转rs485驱动,就是那个东东啦。USB转485驱动程序官方版发布。。驱动压缩包中此外还包含了USB编程电缆驱动程序安装说明-485.doc,喜欢的小伙伴可以下载使用。USB转RS485串口驱动PL2303,适合WIN7/WINXP/LINUX等系统。usb转485转换器线驱动安装方法:1、在安装前可以…

    2022年4月28日
    36
  • 关于前端iframe嵌套页面的跳转问题

    关于前端iframe嵌套页面的跳转问题因工作中遇到的项目,有iframe页面嵌套,遇到了页面跳转的问题,所以记录解决问题的过程关于前端iframe嵌套页面的跳转问题问题:在A页面使用iframe嵌套了B页面,B页面中做了权限校验,即登录成功后才可以访问B中的某个页面,如果没有登录,则跳转A登录页面.过程:开始在B中尝试使用页面跳转location.href=”A登录的页面地址”,一直访问失败,且浏览器地址栏的url也没有变化,查询相关资料得到解决方法.解决方案:使用:windows.parent.location.href=”.

    2022年6月16日
    252

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号