IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 大数据 -> 扫码登录的简单实现 -> 正文阅读

[大数据]扫码登录的简单实现

前言

本文将介绍基于SpringBoot + Vue + Android实现的扫码登录demo的总体思路,完整代码已上传到GitHubWeb端体验地址:http://47.116.72.33/(只剩一个月有效期),apk下载地址:https://github.com/zhangjiwei1221/qrscan/releases/tag/0.0.1。用户名:非空即可,密码:123456,效果见文末,整体实现如有不妥之处,欢迎交流讨论,实现部分参考二维码扫码登录是什么原理

项目简介

后端: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);
    }
    

效果演示

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

  大数据 最新文章
实现Kafka至少消费一次
亚马逊云科技:还在苦于ETL?Zero ETL的时代
初探MapReduce
【SpringBoot框架篇】32.基于注解+redis实现
Elasticsearch:如何减少 Elasticsearch 集
Go redis操作
Redis面试题
专题五 Redis高并发场景
基于GBase8s和Calcite的多数据源查询
Redis——底层数据结构原理
上一篇文章      下一篇文章      查看所有文章
加:2021-09-22 14:44:36  更:2021-09-22 14:44:52 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/23 23:22:47-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码