如何实现图形验证

862 阅读2分钟

svg-captcha

非常好用的图形验证码库,通过一些配置后返回 svg ,并且无需安装任何 C++ 插件。

如何使用

const Controller = require('egg').Controller;
const svgCaptcha = require('svg-captcha');
class CaptchaController extends Controller {
  getCaptcha() {
    const captcha = svgCaptcha.create({
      // 随机字符的数量
      size: 4,
      // 过滤掉一些不容易辨识的字符
      ignoreChars: 'iIl10oO',
      // 干扰线条的数量
      noise: 6,
      // 字符将会有随机颜色而不再是灰色
      color: true,
    });
    // 通过create方法会返回一个对象,其中有两个属性
    // text: 验证码的文本
    // data: svg数据

    // 在session中保存正确的验证码文本,将svg数据返回给客户端
    this.ctx.session.captcha = captcha.text.toLowerCase(); 
    this.ctx.type = 'image/svg+xml';
    this.ctx.body = captcha.data;
  }
}
module.exports = CaptchaController;

新建好 CaptchaController 后我们还需要配置路由 只需在 router.js 中添加

router.get('/captcha', controller.captcha.getCaptcha);

然后前端就可以请求接口获取到图形验证码

检测验证码是否正确

我们可以制作一个中间件来验证验证码是否正确

module.exports = () => async (ctx, next) => {
  try {
    // 从请求体中拿取验证码
    const captcha = ctx.request.body.captcha.toLowerCase();
    // 获取之前保存在session中正确的验证码文本
    const sessionCaptcha = ctx.session.captcha;
    // 去除session中的验证码文本
    ctx.session.captcha = null;
    // 比较用户输入和正确文本是否一致
    if (sessionCaptcha !== captcha) {
      ctx.app.error.throw(406, '验证码错误');
    }
  } catch (e) {
    ctx.logger.error(e);
    ctx.app.error.throw(406, '验证码错误');
  }
  await next();
};

添加路由

router.post(
  '/api/xxx',
  app.middleware.captcha(),
  // 后续操作 比如验证账号密码是否正确等
);

图形验证中session的坑

首页我们先了解一下服务端session的原理

当用户首次与Web服务器建立连接的时候,服务器会给用户分发一个 SessionID作为标识。SessionID是一个由24个字符组成的随机字符串。服务器就会在响应头中添加 Set-Cookie,将这个SessionIDcookie的方式保存在浏览器中,接下来的每次请求时浏览器会携带这个ID,这样服务器就能区分发起请求的是哪一个客户端,才能到拿到session中保存的数据。

所以想要session正常使用,非常关键的一点就是cookie能够携带在请求中。而现在前端常用的 axiosfetch 默认都不会处理cookie,例如在axios中我们需要配置

// 全局配置,任何请求都允许cookie
axios.defaults.withCredentials = true;
// 也可以在指定的请求中允许cookie

服务器cors也需要允许cookie(在config.default.js中配置)

exports.cors = { //解决跨域访问
  // 允许跨域的方法 
  allowMethods: 'GET,HEAD,PUT,POST,DELETE,PATCH,OPTIONS',
  // 允许携带cookie
  credentials: true,
  // 允许跨域的地址,不能为 '*',需指定一个地址
  origin: 'xxx'
};