深入浅出扫码登录

前言

扫码登录是应用中很常见的需求,比如微信,平时你都会使用手机端,当你需要在电脑上登录了,就会展示一个二维码给你,你打开手机微信扫码,进到一个页面,点确认登录即可。

电脑端登录就像是这样的:

手机端扫了以后的页面是这样的:

这中间的过程其实很简单,pc端轮询查二维码的状态,h5端更改二维码状态并在点确认登录动作的时候将已有的登录态,也就是token传给服务端,服务端做一层校验,校验通过把二维码状态改掉,把用户信息返回给正在轮询的PC端即可。

扫码登录的系统工作图就像这样:

下面我们开始用Nest来实现。

先起一个Nest项目。

arduino 复制代码
nest new qrcode-project

把项目跑起来,执行命令,把接口跑通。

arduino 复制代码
npm run start:dev

然后用Nest CLI创建一个通用的扫码模块,执行命令:

nest n mo qrcode
nest n s qrcode
nest n co qrcode

安装一下生成二维码的依赖包。

css 复制代码
npm i qrcode --save-dev

然后开始业务部分的开发,基于上面的时序图,我们再梳理一下,总共需要三个接口就OK了。

对于PC端,我们需要准备创建二维码、查询二维码状态两个动作。

对于H5端,我们只需要在进行了某一步操作一下,把二维码状态同步给服务端即可。

  • create,创建二维码,提供给PC端扫码。
  • getQrcodeStatus,获取二维码状态。
  • updateQrcodeStatus,更新二维码状态。

我们来把这三个接口实现掉吧。

typescript 复制代码
import { BadRequestException, Controller, Post, Get } from '@nestjs/common';
import { randomUUID } from 'crypto';
import * as qrcode from 'qrcode';

const map = new Map<string, QrCodeInfo>();

type QrCodeStatus =
  | 'noscan' // 未扫码
  | 'scan-wait-confirm' // 已扫码,等待确认
  | 'scan-confirm' // 确认
  | 'scan-cancel' // 取消
  | 'expired'; // 过期
interface QrCodeInfo {
  status: QrCodeStatus;
  userInfo?: {
    userId: number;
  };
}

/**
 * @description: PC端获取二维码
 * @return {*}
 */
@Controller('qrcode')
export class QrcodeController {
  @Get('/create')
  async create() {
    const url = 'http://localhost:8000/h5Login';
    const qrcodeId = randomUUID();
    const qrcodeUrl = await qrcode.toDataURL(url);
    map.set(qrcodeId, {
      status: 'noscan',
    });
    return {
      qrcodeId,
      qrcodeUrl,
    };
  }

  /**
   * @description: PC端轮询获取
   * @return {*}
   */
  @Get('/getQrcodeStatus')
  async getQrcodeStatus(qrcodeId: string) {
    const info = map.get(qrcodeId);
    if (info.status === 'scan-confirm') {
      return info.userInfo;
    }
    return {
      status: info.status,
    };
  }

  /**
   * @description: H5端修改二维码状态(扫码、确认登录)
   * @return {*}
   */
  @Post('/updateQrcodeStatus')
  async updateQrcodeStatus(qrcodeId: string, status: QrCodeStatus) {
    const info = map.get(qrcodeId);
    if (!info) {
      throw new BadRequestException('二维码已过期');
    }
    info.status = status;
    return {
      status: info.status,
    };
  }
}

/qrcode/create会返回一个二维码图片地址和二维码的ID,PC端展示即可,二维码的链接就是H5登录的页面,可以选择把二维码ID带到url query里去查询。

然后PC端就开始跑轮询任务,每次带着这个二维码ID去查就行,可以做一些更新的交互动作,拿掘金举例子,就像是这样:

扫码成功:

登录成功:

对于/qrcode/updateQrcodeStatus,在H5端如果点了取消或者确认,就传不同的二维码status枚举给服务端同步即可。

前端准备了一个类似的测试页面:

当点击确认登录,二维码更新到确认的状态后,我们还需要返回用户信息给PC端,那这里就需要做登录鉴权操作。

对于jwt还会涉及到伪造请求的安全问题,可以看这篇文章:

伪造请求解决方案

我们安装jwt包。

bash 复制代码
npm install @nestjs/jwt

app模块中导入:

然后我们需要改造/qrcode/updateQrcodeStatus/qrcode/getQrcodeStatus,当H5确认登录,服务端需要先做一层token校验,如果验证通过,就把用户信息存在二维码表中,就像这样:

typescript 复制代码
/**
   * @description: H5端修改二维码状态(扫码、确认登录)
   * @return {*}
   */
  @Post('/updateQrcodeStatus')
  async updateQrcodeStatus(
    qrcodeId: string,
    status: QrCodeStatus,
    @Headers('Authorization') auth: string,
  ) {
    const info = map.get(qrcodeId);
    if (!info) {
      throw new BadRequestException('二维码已过期');
    }
    if (status === 'scan-confirm' && auth) {
      // 确认登录
      try {
        const [, token] = auth.split(' ');
        const info = await this.jwtService.verify(token);
        const user = users.find((item) => item.id == info.userId);
        info.userInfo = user;
        return user;
      } catch (e) {
        throw new UnauthorizedException('token 过期,请重新登录');
      }
    }
    info.status = status;
    return {
      status: info.status,
    };
  }

如果二维码更新成功了,PC端下一次轮询就可以拿到确认的二维码状态,并且可以拿到userInfo,在基于用户信息返回一个token给PC端即可,这样就登录成功了。

typescript 复制代码
  /**
   * @description: PC端轮询获取
   * @return {*}
   */
  @Get('/getQrcodeStatus')
  async getQrcodeStatus(qrcodeId: string) {
    const info = map.get(qrcodeId);
    if (info.status === 'scan-confirm') {
      const accsess_token = this.jwtService.sign(info.userInfo);
      return { userInfo: info.userInfo, accsess_token };
    }
    return {
      status: info.status,
    };
  }

扫码登录就实现了,其实原理很简单,只是涉及到了跨端,会觉得比较复杂。

总结

扫码登录是一种便捷的身份验证方式,用户通过扫描屏幕上显示的二维码(QR码)来登录应用或网站。这种登录方式在移动设备普及的背景下变得越来越流行,因为它提供了一个无需记忆密码的安全登录选项。

扫码登录的主要思路可以概括为以下几个步骤:

  1. 二维码的展示

    当用户想要登录时(通常在Web应用或者PC软件中),服务器会生成一个包含特定登录信息的二维码并展示给用户。

  2. 二维码的扫描

    用户使用手机上的应用(例如移动端的App内置扫码功能或通用的二维码扫码应用)扫描二维码。扫码功能得以识别出二维码内包含的登录信息。

  3. 登录验证

    移动应用识别二维码中的信息后,一般包含认证服务器的URL和一段用于两端验证的token。移动应用通过HTTPS协议将token发送到服务器进行验证。

  4. 会话创建

    服务器验证token的有效性,若验证通过,则会在服务器端创建一个新的登录会话,并返回会话信息给Web应用或PC软件。

  5. 登录确认

    桌面端或Web端接收到服务器响应的登录会话之后,更新用户界面,确认用户已经登录成功。

    如果喜欢我的文章或者想上岸大厂,可以关注公众号「量子前端」,将不定期关注推送前端好文、分享就业资料秘籍,也希望有机会一对一帮助你实现梦想。

相关推荐
twins352043 分钟前
解决Vue应用中遇到路由刷新后出现 404 错误
前端·javascript·vue.js
Yvemil71 小时前
MQ 架构设计原理与消息中间件详解(二)
开发语言·后端·ruby
qiyi.sky1 小时前
JavaWeb——Vue组件库Element(3/6):常见组件:Dialog对话框、Form表单(介绍、使用、实际效果)
前端·javascript·vue.js
煸橙干儿~~1 小时前
分析JS Crash(进程崩溃)
java·前端·javascript
2401_854391081 小时前
Spring Boot大学生就业招聘系统的开发与部署
java·spring boot·后端
安冬的码畜日常1 小时前
【D3.js in Action 3 精译_027】3.4 让 D3 数据适应屏幕(下)—— D3 分段比例尺的用法
前端·javascript·信息可视化·数据可视化·d3.js·d3比例尺·分段比例尺
虽千万人 吾往矣1 小时前
golang gorm
开发语言·数据库·后端·tcp/ip·golang
l1x1n02 小时前
No.3 笔记 | Web安全基础:Web1.0 - 3.0 发展史
前端·http·html
昨天;明天。今天。2 小时前
案例-任务清单
前端·javascript·css
这孩子叫逆2 小时前
Spring Boot项目的创建与使用
java·spring boot·后端