nest.js实现验证码功能

1、npm下载需要的包

安装session包

npm i express-session
npm i -D @types/express-session

安装验证码包

//安装包
npm i svg-captcha
//安装ts代码提示
npm i -D @types/@svg-captcha

 2、定义session

Session(会话)

import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';

import * as session from 'express-session';

async function bootstrap() {
  const app = await NestFactory.create(AppModule);

  app.use(
    session({
      secret: 'my-secret',
      resave: false,
      saveUninitialized: false,
      rolling: true, //在每次请求时强行设置 cookie,这将重置 cookie 过期时间(默认:false)
    }),
  );

  app.setGlobalPrefix('api');

  await app.listen(3000);
}
bootstrap();

3、实现验证码接口

import {
  Controller,
  Get,
  Post,
  Body,
  Patch,
  Param,
  Delete,
  Session,
} from '@nestjs/common';
import * as svgCaptcha from 'svg-captcha';

......

 @Get('captchaImage')
  getCode(@Session() session) {
      const captcha = svgCaptcha.create({
      size: 4, //验证码长度
      fontSize: 50,
      width: 110,
      height: 38,
      background: '#cc9966', //背景颜色
    });
    session.code = captcha.text; //session保存验证码
    return { img: captcha.data }; // aptcha.data  返回的是svg图
  }

4、前端请求获取到验证码

<template>
  <div class="login-code">
    <!-- <img :src="codeUrl" @click="getCode" class="login-code-img" /> -->
    <div @click="getCode" v-html="codeUrl" class="login-code-img"></div>
  </div>
</template>

<script>
import request from '@/utils/request'

export default {
  name: '',
  data() {
    return { codeUrl: '' }
  },
  created() {
    this.getCode()
  },
  methods: {
    getCode() {
      request({
        url: '/login/captchaImage',
        headers: {
          isToken: false
        },
        method: 'get',
        timeout: 20000
      }).then((res) => {
        this.codeUrl = res.img
      })
    }
  }
}
</script>

<style>
</style>

5、验证验证码(登录接口)

import {
  Controller,
  Get,
  Post,
  Body,
  Patch,
  Param,
  Delete,
  Session,
} from '@nestjs/common';

import * as svgCaptcha from 'svg-captcha';


@Controller('login')
export class LoginController {
  constructor(private readonly loginService: LoginService) {}

  // 获取验证码
  @Get('captchaImage')
  getCode(@Session() session) {
    const captcha = svgCaptcha.create({
      size: 4, //验证码长度
      fontSize: 50,
      width: 110,
      height: 38,
      background: '#cc9966', //背景颜色
    });
    session.code = captcha.text; //session保存验证码
    return { img: captcha.data }; // aptcha.data  返回的是svg图
  }

  //登录接口
  @Post('signUp')
  signUp(@Session() session, @Body() body) {
    // 前端传回来的验证码,转换成小写
    const code = body.code.toLowerCase();
    //get方式获取的验证码定义的
    const sessionCode = String(session.code).toLowerCase();
    console.log(code, sessionCode);
    if (sessionCode === code) {
      return {
        code: 200,
        msg: '验证码正确',
        data: {},
      };
    } else {
      return {
        code: 601,
        msg: '验证码错误',
        data: {},
      };
    }
  }

 
}

注意:如果你是使用core来解决跨域问题,这时就会出现登录接口的session上没有保存之前验证码获取的code这个属性。

原因:

第一次接到请求,后关会生成一个sessionid来标识当前会话(我使用express-session来实现),并通过set-cookie响应头在客户端生一个cookie,大概长这样,

connect.sid=s%3Au9xG34DBU1vOVbIpCax0neMxL_Uc1fIC.4ndNJL5G%2B41DtUSLbQ%2BW75Z9wduOAON4lfu2JGTDe5
由于cookie会自动发送给服务器,所以当前用户后面的所有请求都会携带这个sessionid给服务器,服务器通过这个sessionid来标识是否为同一个用户,

但是默认情况下,标准的跨域请求是不会发送cookie等用户认证凭据的,同样,后端通过Set-Cookie在跨域时默认是被浏览器忽略的,所以这个登录接口的session跟之前验证码获取接口的session是不同的。

解决办法:

方法一

前端发起请求时设置 withCredentials:true 请求头

设置该请求头后,后端Access-Control-Allow-Origin的值不能设置为 *,必须设置为具体域名
 

// 创建axios实例
const service = axios.create({
  // axios中请求配置有baseURL选项,表示请求URL公共部分
  baseURL: process.env.VUE_APP_BASE_API,
  // 超时
  timeout: 10000,
  withCredentials: true, // 设置跨域,是否提供凭据信息(cookie、HTTP认证及客户端SSL证明等),且服务端要设置Access-Control-Allow-Origin,告诉浏览器允许跨域,而且这个值必须指定域名,不能设置为*
})
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';

import * as session from 'express-session';

async function bootstrap() {
  const app = await NestFactory.create(AppModule);

  app.enableCors({
    //  定义了被允许的请求头列表。这里指定了几个常见的请求头(如果请求头里面的自定义字段也要加上去)
    allowedHeaders: [
      'Accept',
      'Accept-Version',
      'Content-Type',
      'Api-Version',
      'Origin',
      'X-Requested-With',
      'Authorization',
      'istoken', // 添加istoken字段
    ],
    //定义了允许跨域访问的来源。可以是一个字符串,也可以是一个字符串数组
    origin: ['http://localhost:8080'],
    // 指定了是否允许跨域请求携带认证信息,如 Cookies、Authorization 等
    credentials: true,
    //  定义了响应头中可以被客户端读取的字段列表。这里只列出了一个字段名 API-Token-Expiry。
    exposedHeaders: ['API-Token-Expiry'],
  });

  app.use(
    session({
      secret: 'my-secret',
      resave: false,
      saveUninitialized: false,
      rolling: true, //在每次请求时强行设置 cookie,这将重置 cookie 过期时间(默认:false)
    }),
  );

  app.setGlobalPrefix('api');

  await app.listen(3000);
}
bootstrap();

方法二

使用vue的代理来解决跨域就没有以上的那些事。。。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值