基于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端接收到服务器响应的登录会话之后,更新用户界面,确认用户已经登录成功。

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

相关推荐
新缸中之脑3 分钟前
Llama 3.2 安卓手机安装教程
前端·人工智能·算法
hmz8566 分钟前
最新网课搜题答案查询小程序源码/题库多接口微信小程序源码+自带流量主
前端·微信小程序·小程序
看到请催我学习12 分钟前
内存缓存和硬盘缓存
开发语言·前端·javascript·vue.js·缓存·ecmascript
blaizeer41 分钟前
深入理解 CSS 浮动(Float):详尽指南
前端·css
速盾cdn1 小时前
速盾:网页游戏部署高防服务器有什么优势?
服务器·前端·web安全
小白求学11 小时前
CSS浮动
前端·css·css3
什么鬼昵称1 小时前
Pikachu-csrf-CSRF(POST)
前端·csrf
XiaoYu20022 小时前
22.JS高级-ES6之Symbol类型与Set、Map数据结构
前端·javascript·代码规范
golitter.2 小时前
Vue组件库Element-ui
前端·vue.js·ui
儒雅的烤地瓜2 小时前
JS | JS中判断数组的6种方法,你知道几个?
javascript·instanceof·判断数组·数组方法·isarray·isprototypeof