vue中使用验证码和问题处理

vue中使用验证码

1、思路分析

后端分析:

  1. nodejs的第三方express 开启服务器
  2. 数据接口 —— 包括登录接口、图形验证接口
  3. 图形验证码的处理封装
  4. 前后端跨域处理(这里处理的是vue和静态资源服务器端口不同导致验证码跨域问题)

前端分析:

  1. vue框架 登录视图页面
  2. axios 发送请求(这里是封装了axios) 获取数据

2、npm安装必要的包

npm i express 开启静态资源服务器
npm i mongodb 连接数据库
npm i express-session 会话session
npm i svg-captcha 图形验证码
npm i jsonwebtoken 用于令牌的token

注意以下以 nodejs 和 mongodb 来后端和数据库开发使用,以 nodejs 的第三方 express 开启静态资源服务器使用

3、express开启服务器和封装必要的请求函数

server.js

const express = require('express');
const {
  PORT
} = require('./config.json');
const rootRouter = require('./router');
const path = require('path')

//创建一个服务器
const app = express();

//  ./ 是当前目录 
//   / 是根目录
// 启用静态资源服务器
app.use(express.static(path.join(__dirname, './public'), {
  // maxAge:60*60*1000*24
}));

// 数据接口
app.use('/api', rootRouter);

app.listen(PORT, () => {
  console.log('server is running on port ' + PORT)
})

router文件下的index.js

const {
  Router,
  urlencoded,
  json
} = require('express');
// express.json===bodyParse.json, ....

const session = require('express-session')
const token = require('../utils/token');
const cors = require('../utils/cors')

const router = Router();

const userRouter = require('./user');
const goodsRouter = require('./goods');

const regRouter = require('./reg');
const loginRouter = require('./login');

const vcodeRouter = require('./vcode');
const uploadRouter = require('./upload');

const {
  formatData
} = require('../utils/tools');

// 跨域请求处理
router.use(cors)

// 数据格式化中间件
router.use(urlencoded({
  extended: false
}), json())

// 使用session会话
// 通过req.session获取存入会话的数据
router.use(session({
  secret: 'fqniu',
  resave: false,
  saveUninitialized: true,
  cookie: {
    // 设置cookie有效期
    maxAge: 1000 * 60 * 60 * 2
  }
}))

// /api/user
router.use('/user', userRouter);

// /api/goods
router.use('/goods', goodsRouter);

// 注册
router.use('/reg', regRouter);

// 登录
router.use('/login', loginRouter);

// 上传
router.use('/upload', uploadRouter);

// 校验token
router.get('/jwtverify', (req, res) => {
  const {
    authorization
  } = req.query;
  console.log('test', authorization)

  // verify方法校验成功:得到一个对象
  // verify方法校验不通过:直接抛出错误
  // try{
  //     var decoded = jwt.verify(authorization, 'laoxie');
  //     res.send(formatData())
  // }catch(err){
  //     res.send(formatData({code:0}))
  // }

  if (token.verify(authorization)) {
    res.send(formatData())
  } else {
    res.send(formatData({
      code: 0
    }))
  }
});

// 验证码
router.use('/vcode', vcodeRouter);


module.exports = router;

router文件下的mongodb.js

/**
 * MongoDB操作封装
 */
const {
  MongoClient,
  ObjectId
} = require('mongodb');

// mongodb数据库地址
const url = 'mongodb://localhost:27017';

// 数据库名称
const dbName = 'user';

async function connect() {
  const client = await MongoClient.connect(url);
  const db = client.db(dbName);
  return {
    client,
    db
  }
}


// 增
async function insert(colName, data) {
  // 1. 连接数据库
  const {
    db,
    client
  } = await connect();
  // 2. 添加数据

  // 根据传入的集合名称获取数据库中的某个集合
  const collection = db.collection(colName);

  const result = await collection[Array.isArray(data) ? 'insertMany' : 'insertOne'](data)

  // 3. 关闭连接
  client.close()
  return result;
}

// 删
async function remove(colName, query) { // query{_id:'5c128cdbd1233ce12c878a32'}
  const {
    db,
    client
  } = await connect();

  if (query._id && typeof query._id === 'string') {
    query._id = ObjectId(query._id);
  }

  const collection = db.collection(colName);
  const result = await collection.deleteMany(query);

  client.close();
  return result;
}

// 改
async function update(colName, query, newData) { // newData{$set:{price:200,qty:2},$inc:{view:1}}
  const {
    db,
    client
  } = await connect();

  const collection = db.collection(colName);

  if (query._id && typeof query._id === 'string') {
    query._id = ObjectId(query._id);
  }

  const result = await collection.updateMany(query, newData);

  return result;
}

// 查  query 查询语句
async function find(colName, query = {}, options = {}) { // options={litmit:10,skip:0}
  const {
    client,
    db
  } = await connect();
  // console.log("query=", query)

  const collection = db.collection(colName);

  if (query._id && typeof query._id === 'string') {
    query._id = ObjectId(query._id);
  }

  // // 查询多少条数据
  let datalength = await collection.count().then(data => data)
  // console.log("数据长度=", datalength) // 50
  // 添加条数
  let total = datalength
  // console.log("这个1=", result)

  // 如果存在query查询到的 name 则执行下面语句
  if (query.name) {
    const font = query.name
    let result = collection.find({
      'name': {
        '$regex': font,
        // "$options": 'i'
      }
    });
    result = await result.toArray();
    client.close();
    return {
      result,
      total
    }
  }

  // 查询到数据集合
  let result = collection.find(query); // 50->10

  // 判断是否要跳过记录
  if (options.skip) {
    result = result.skip(options.skip)
  }

  if (options.limit) {
    result = result.limit(options.limit);
  }

  // 排序
  // console.log('sort',options.sort);
  if (options.sort) { //['price'],['price','1']
    let key, val;
    key = options.sort[0];
    if (options.sort.length > 1) {
      val = options.sort[1] * 1;
    } else {
      val = -1;
    }
    result = result.sort({
      [key]: val
    })
  }

  result = await result.toArray();
  // console.log(result)
  client.close();
  
  // 把数据和数据长度返回出去
  return {
    result,
    total
  }
}


module.exports = {
  insert,
  remove,
  update,
  find
}

router文件下的vcode.js

const express = require('express');
const router = express.Router();
const svgCaptcha = require('svg-captcha');

const {
  formatData
} = require('../utils/tools');

// 生成验证码
router.get('/', async (req, res) => {
  // 生成图像验证码:svg-captcha
  const options = {
    // size: 10,
    noise: 3,
    ignoreChars: '0o1il',
    background: '#58bc58',
    color: true,
    fontSize: 50,
    height: 54
  }

  // 验证码在这里生成
  const captcha = svgCaptcha.create(options); // {data:'<svg/>',text:'abcd'}

  //  console.log('vcode.session=',req.session);
  // 把验证码存入会话Session
  req.session.vcode = captcha.text.toLowerCase();

  res.send(formatData({
    data: captcha.data
  }));
})

module.exports = router;

router文件下的token.js

const jwt = require('jsonwebtoken');

const privateKey = 'fqniu';

function create(data = {}, expiresIn = '2h') {
  const token = jwt.sign({
    ...data
  }, privateKey, {
    // token有效期
    expiresIn
  });
  return token;
}

function verify(token) {
  let result;
  try {
    jwt.verify(token, privateKey);
    result = true;
  } catch (err) {
    result = false
  }
  return result;
}

module.exports = {
  create,
  verify
}

router文件下的login.js

const express = require('express');
const router = express.Router();

// 引入 封装的 token 模块
const token = require('../utils/token');

const {
  formatData,
  md5
} = require('../utils/tools');
const mongo = require('../utils/mongo');

// 登录 get请求
router.get('/', async (req, res) => {
    let { username, password, vcode, mdl } = req.query;
    // 其中vcode是前端输入的验证码
    // 从会话中获取验证码
    // 校验验证码 这里的req.session 是已经存有vcode的 Session 对象
    // console.log('login.session=', req.session)
    // vcode 是前端输入验证码之后,传过来的参数 
    // 而 req.session.vcode 是Session中的存的 vcode
    if (vcode !== req.session.vcode) {
        res.send(formatData({ code: 10 }))
        return;
    }
    // 加密后进行查询
    // password = md5(password)
    let {result} = await mongo.find('user', { username, password }); //[{}]
    console.log("result=",result)
    // 判断如果 find 找到 有result, 则返回这个数组数据
    // 如果find 找不到 ,则result为 空数组
    if (result.length > 0) {
        // 用户名、密码、验证码都校验通过后,判断是否有免登陆选项
        // console.log('req.query=', req.query);
        let authorization;
        if (mdl === 'true') {
            // token的操作
            // 1. 生成token
            // const token = jwt.sign({ username }, 'laoxie' ,{
            //     // token有效期
            //     expiresIn: 20//1000 * 60 * 60 * 24 * 7
            // });
            //封装的 token 函数
            authorization = token.create({ username }, '7d')
        }else{
            authorization = token.create({ username })
        }
        // console.log('token=', authorization);
        result = result[0];
        result.authorization = authorization
        res.send(formatData({ data: result }));
    } else {
        res.send(formatData({ code: 0 }))
    }
})

module.exports = router;

router文件下的cors.js
用于跨域处理

const allow_origin = ['localhost:3000', 'www.xxxxxx.com']

function cors(req, res, next) {
  // 设置响应头
  // Access-Control-Allow-Origin
  // Access-Control-Allow-Methods
  // Access-Control-Allow-Headers
  // res.header("Access-Control-Allow-Origin", "*");
  // res.header("Access-Control-Allow-Headers", "Content-Type,Content-Length, Authorization, Accept,X-Requested-With");
  // res.header("Access-Control-Allow-Methods","PUT,POST,GET,PATCH,DELETE,OPTIONS");
  // console.log('Origin:',req.get('host'));

  // 获取请求者的域名
  if (allow_origin.includes(req.get('host'))) {
    res.set({
      "Access-Control-Allow-Origin": "http://localhost:8080",
      "Access-Control-Allow-Headers": "Content-Type,Content-Length, Authorization, Accept,X-Requested-With",
      "Access-Control-Allow-Methods": "PUT,POST,GET,PATCH,DELETE,OPTIONS",
      "Access-Control-Allow-Credentials": true
    })
    // 跨域请求CORS中的预请求
    if (req.method == "OPTIONS") {
      res.sendStatus(200); /*让options请求快速返回*/
    } else {
      next();
    }
  } else {
    res.send(401);
  }
}

module.exports = cors;

router文件下的tools.js
用于对请求数据formatData的封装

const crypto = require('crypto');

function formatData({code=1, data=[], msg='success'}={}){

    if(code === 0){
      msg = 'fail';
    }

    return {
      code,
      data,
      msg
    }
}

// 封装加密函数
function md5(data,privateKey='fqniu'){

  const hash = crypto.createHash('md5');
  hash.update(data + privateKey); // 加盐 盐值
  const result = hash.digest('hex');
  return result;
}


module.exports = {
  formatData,
  md5
}

4、前端准备工作

<template>
  <div class="login-wrap">
    <el-form class="login-form" :model="ruleForm" status-icon ref="ruleForm" label-width="100px">
      <h1>登录账号</h1>
      <el-form-item label="用户名" prop="username">
        <el-input type="text" v-model="ruleForm.username" autocomplete="off"></el-input>
      </el-form-item>
      <el-form-item label="密码" prop="password">
        <el-input type="password" v-model="ruleForm.password" autocomplete="off"></el-input>
      </el-form-item>
      <el-form-item label="验证码" class="vcode-input">
        <el-input placeholder="请输入验证码" v-model="ruleForm.vcode">
          <!-- 验证码html结构 -->
          <template v-slot:append>
            <div v-html="vcodeSvg" @click="vcode()" class="vcode"></div>
          </template>
        </el-input>
      </el-form-item>
      <el-form-item>
        <el-button type="primary" @click.prevent="submitForm()">登录</el-button>
        <el-button @click="resetForm">重置</el-button>
        <el-button @click="gotoLogin">立即注册</el-button>
      </el-form-item>
    </el-form>
  </div>
</template>

<script>
export default {
  data() {
    return {
      // from表单 参数 需要发请求到后端进行处理, 包括用户名、密码、验证码
      ruleForm: {
        username: "admin",
        password: "123456",
        vcode: "",
      },
      // 定义变量接收请求后的图形验证码
      vcodeSvg: "",
    };
  },
  methods: {
    // 登录请求
    async submitForm() {
      const { data } = await this.$request.get("/login", {
        // 因为后端需要 query 来接收参数,所以这里需要传入params { 里面放出去的参数 }
        // 这是对象需要结构
        params: {
          ...this.ruleForm,
        },
      });
      console.log(data);
      if (data.code === 1) {
        // 登录成功
        // 保存token
        localStorage.setItem("token", data.data.authorization);
        this.$message({
          type: "success",
          message: "登录成功",
        });
        // 登录成功跳转到home
        this.$router.push("/home");
      } else if (data.code === 10) {
        // 登录失败
        this.$message({
          type: "error",
          message: "验证码有误",
        });
      } else {
        this.$message({
          type: "error",
          message: "账号和密码有误",
        });
      }
    },
    // 重置表单数据
    resetForm() {
      this.ruleForm.username = "";
      this.ruleForm.password = "";
    },
    // 点击注册按钮, 跳转到注册
    gotoLogin() {
      this.$router.push("/reg");
    },
    // 点击更新验证码
    async vcode() {
      const { data } = await this.$request.get("/vcode");
      // console.log(data);
      this.vcodeSvg = data.data;
    },
  },
  // vue生命周期中created函数, 一加载页面时就发请求 显示图形验证码 并把返回的数据渲染到页面上
  // 因为后端返回的是图形验证码的html
  created() {
    this.vcode();
  },
};
</script>

<style>
.login-wrap {
  height: 100%;
  background: #ccc;
  display: flex;
  justify-content: center;
  align-items: center;
}
.login-form {
  width: 520px;
  padding: 20px;
  background: #fff;
  border-radius: 10px;
}
.el-input .el-input-group__append {
  padding: 0;
  border: 0;
}
.vcode svg {
  width: 110px;
  height: 40px;
}
</style>

效果图:
在这里插入图片描述

5、遇到问题

但是虽然验证码获取数据渲染页面成功,发送登录请求,但是后端却返回验证码有误,经过多次验证和查资料,才发现的问题如下:

问题如图:
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

5、问题处理

这里是处理图形验证码后端session中获取不到的问题(跨域)也就是 跨域导致 set-cookie 无效
推荐博客跨域set-cookie无效

后端处理cors跨域:
在这里插入图片描述
前端处理axios发送请求
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
还有一种是前后端已经设置了,但是还是拿不到vcode怎么办?

最终发现是浏览器因为安全策略给禁用了cookie,比如谷歌的 chrome://flags/ 里面的设置
在这里插入图片描述

  • 1
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值