前端验证码方案小结

前言

七月份发生了很多事情,然后精力基本上也没多少放在研究新技术上,除了日常工作,还请了一个星期假回老家订婚,领证。所以这篇文章会比较简单。

正文

作为前端我们常常会遇到一些场景需要用到验证码,在过去我们会比较依赖后端的服务。但是有了node我们基本上都能自己来。但是涉及到验证码就肯定会涉及到安全的问题。

因此我们不可能仅仅只是纯粹的展示验证码和校验。

方案1

通过 svg-captcha 生成验证码,然后将验证码文本通过唯一的 csrf token 存储到redis服务。

前端得到svg的验证码显示,然后将输入的验证码通过接口传递给node端判断是否正确。

node端通过csrf读取redis保存的验证码。

方案2

通过 svg-captcha 生成验证码,然后将验证码文本以对称加密方式一起传给前端。前端直接校验。

代码

获取验证码 参考代码如下:


// 返回用户验证码图像
export const verifyCode = async (ctx, next) => {
  const fontSize = ctx.query.size || 40;
  const width = ctx.query.w || 150;
  const height = ctx.query.h || 50;
  svgCaptcha.options.fontSize = fontSize; // captcha的宽度
  svgCaptcha.options.width = width; // 验证码的高度
  svgCaptcha.options.height = height; // captcha文本大小
  // svgCaptcha.options.charPreset = charPreset; // 随机字符预设
  const captcha = svgCaptcha.create({
    size: 4,
    // 验证码去掉模糊人眼容易认错的字符
    ignoreChars: '0o1iLlI',
    noise: 2,
    color: false
  });
  const csrf = ctx.request.body.data.csrf || 'verifyCodeSecret';
  const key = cipher(captcha.text.toLowerCase(), 'RC4', csrf);
  ctx.body = {
    status: '0',
    result: 'success',
    data: {
      svg: captcha.data,
      key: key
    }
  };
  return;
};

加密代码


import * as CryptoJS from 'crypto-js';
export const cipher = (str, method, keyStr) => {
  const key = CryptoJS.enc.Utf8.parse(keyStr);
  const encryptedData = CryptoJS[method].encrypt(str, key);
  const encryptedStr = encryptedData.ciphertext.toString();

  return encryptedStr;
};

export const decipher = (data, method, keyStr) => {
  if (!(data instanceof Object)) return data;
  const key = CryptoJS.enc.Utf8.parse(keyStr);
  const result = Object.assign({}, data);
  Object.keys(data).forEach(element => {
    const encryptedHexStr = CryptoJS.enc.Hex.parse(data[element]);
    const encryptedBase64Str = CryptoJS.enc.Base64.stringify(encryptedHexStr);
    const decryptedData = CryptoJS[method].decrypt(encryptedBase64Str, key);
    const decryptedStr = decryptedData.toString(CryptoJS.enc.Utf8);

    result[element] = decryptedStr;
  });
  return result;
};

前端react引用

因为方法二返回的svg是纯代码的,无法直接插入页码,需要通过特殊方式显示 <span dangerouslySetInnerHTML=></span>

Table of Contents