基于Nest实现扫码登录

前言

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

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

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

这中间的过程其实很简单,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端接收到服务器响应的登录会话之后,更新用户界面,确认用户已经登录成功。

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

相关推荐
Martin -Tang34 分钟前
vite和webpack的区别
前端·webpack·node.js·vite
迷途小码农零零发35 分钟前
解锁微前端的优秀库
前端
王解1 小时前
webpack loader全解析,从入门到精通(10)
前端·webpack·node.js
老码沉思录1 小时前
写给初学者的React Native 全栈开发实战班
javascript·react native·react.js
我不当帕鲁谁当帕鲁2 小时前
arcgis for js实现FeatureLayer图层弹窗展示所有field字段
前端·javascript·arcgis
那一抹阳光多灿烂2 小时前
工程化实战内功修炼测试题
前端·javascript
放逐者-保持本心,方可放逐2 小时前
微信小程序=》基础=》常见问题=》性能总结
前端·微信小程序·小程序·前端框架
毋若成5 小时前
前端三大组件之CSS,三大选择器,游戏网页仿写
前端·css
红中马喽5 小时前
JS学习日记(webAPI—DOM)
开发语言·前端·javascript·笔记·vscode·学习
Black蜡笔小新6 小时前
网页直播/点播播放器EasyPlayer.js播放器OffscreenCanvas这个特性是否需要特殊的环境和硬件支持
前端·javascript·html