Nest:登录添加图片验证码并进行效验

237 阅读3分钟

在Web应用中,为了防止恶意的自动化攻击(如暴力破解密码),通常会添加验证码机制。本文将详细介绍如何在NestJS应用中实现图片验证码功能,并进行效验。我们将分步骤讲解如何生成验证码、展示在前端以及效验用户输入的验证码。

生成验证码

首先,我们需要安装验证码包svg-captcha来生成图片验证码

yarn add svg-captcha

定义验证码controller

import {
  Controller,
  Get,
  Inject,
  Request,
  Res,
  Session
} from '@nestjs/common';
import { Response } from 'express';

import { CaptchaService } from '../services/captcha.service';

@Controller('captcha')
export class CaptchaController {
  @Inject(CaptchaService)
  private captchaService: CaptchaService;

  @Get('svg')
  getSvgCaptcha(@Res() response: Response, @Session() session) {
    try {
      const { data, text } = this.captchaService.createSvgCaptcha();
      session.code = text; // 存储验证码文本在session中
      response.set('Content-Type', 'image/svg+xml');
      response.send(data);
    } catch (error) {
      console.error(error);
    }
  }
}
  • 在上述代码中我们在session中设置code的值为验证码的值text,可以用于登录时效验验证码是否正确;如果说,你遇到如下错误,说明在你的项目中并未初始化Session对象
Cannot set properties of undefined (setting 'code')
  • 那么你需要安装express-session或类似的Session中间件,并进行配置:
yarn add express-session
  • 然后在main.ts中进行配置:
// 已省略部分代码
import * as session from 'express-session';

app.use(
    session({
      secret: process.env.SESSION_SECRET,
      resave: false, // 表示如果session对象没有被修改,不会强制重新保存。
      saveUninitialized: false, // 表示不会为未初始化的session对象保存到存储中。
      // 同时设置rolling、maxAge,确保只要用户与服务器保持活动,他们的session就不会过期。
      rolling: true, //在每次请求时强行设置 cookie,这将重置 cookie 过期时间(默认:false)
      cookie: { maxAge: 5 * 60 * 1000 } // 5分钟过期时间
    })
  );
  • 设置响应的Content-Typeimage/svg+xml,这里将svg作为一个图片资源,可在img标签的src中进行使用,如:
<img src="/captcha" alt="Captcha">

定义service

使用svgCaptcha.create()方法可以生成svg的相关数据,datasvg的具体代码数据,而text则是具体的验证码

image.png

image.png

import { Injectable } from '@nestjs/common';
import * as svgCaptcha from 'svg-captcha';

@Injectable()
export class CaptchaService {
  createSvgCaptcha() {
    const captcha = svgCaptcha.create({
      size: 4, // 验证码长度
      noise: 3, // 干扰线条的数量
      width: 100, // 验证码宽度
      height: 40, // 验证码高度
      fontSize: 40, // 字体大小
      color: true // 是否启用颜色
      // background: '#f2f2f2' // 背景颜色
    });
    return {
      data: captcha.data,
      text: captcha.text.toLowerCase() // 转换为小写或进行其他处理
    };
  }
}

前端展示

要在前端显示后端返回的SVG代码,有几种方法可以实现。这些方法取决于你如何获取SVG数据,以及你希望在前端如何处理和显示它。

使用<img>标签的src属性

可以将SVG数据作为一个图片资源来处理,将后端返回的SVG代码放入<img>标签的src属性中。你需要确保后端的API端点返回SVG数据,并设置正确的Content-Typeimage/svg+xml。部分代码如下:

<img
  class="ml20"
  style="width: 100px; height: 40px"
  :src="codeUrl"
  alt=""
  @click="setCodeUrl"
/>

let codeUrl = ref<any>('')
const setCodeUrl = () => {
  codeUrl.value = `api/captcha/svg?.=${Math.random()}`
}
onMounted(() => {
  setCodeUrl()
})

在上述代码中,img标签的src会向api/captcha/svg?.=${Math.random()}发起请求,后端返回相应的图片数据,并显示。

image.png

image.png

后端效验

后端效验即将前端传过来的code与存在sessioncode相比较是否一致即可

@Post('login')
  @ApiOperation({
    summary: '登录' // 接口描述信息
  })
  async userLogin(@Body() loginUser: LoginUserDto, @Session() session) {
    if (loginUser.code !== session.code) {
      throw new BusinessException({
        code: BUSINESS_ERROR_CODE.COMMON,
        message: '验证码错误'
      });
    }
    const userInfoVo = await this.userService.login(loginUser);
    return userInfoVo;
  }