`
天梯梦
  • 浏览: 13630996 次
  • 性别: Icon_minigender_2
  • 来自: 洛杉矶
社区版块
存档分类
最新评论

实现动态验证码的思路

 
阅读更多

示例

示例1

示例2

 

背景

验证码主要是防止机器暴力破解。之前的验证码都是以静态为主,现在一些产品开始使用动态方式,增加破解的难度。动态方式以 gif 最为简单可靠。gif 兼容性好,尺寸小。这里分享的就是一种:用 JS 实现 gif 动态验证码的思路。感谢关注。

 

任务分解

  1. 绘制旋转的文字
  2. 计算每个字符出现位置和角度
  3. 生成 gif 图片

 

逐步求精

如何绘制旋转的文字?

 

了解能用的 API

  • context.rotate(angle) 使当前坐标系旋转 angle,单位弧度
  • context.translate(x, y) 使当前坐标系偏移 x, y,单位像素
  • context.font 设置字体
  • context.strokeText(text, x, y [, maxWidth ]) 给文本描边
  • context.fillText(text, x, y [, maxWidth ]) 给文本填充

怎么以文字的中心位置旋转?

void function() {
  // ...
  var x = 100;
  var y = 100;
  var angle = 1 / 8 * Math.PI;
  context.translate(x, y);
  context.rotate(angle);
  context.strokeText('A', 0, 0);
  // ...
}()

 

以文字的左下角为圆心旋转,不符合预期,见下图效果

示例1

本打算做一下偏移的计算,一想到要计算文本中心位置貌似还挺复杂。 还是看看其他人怎么做的,通过关键词 canvas rotate text center 找到一点线索。

context.save();
context.translate(newx, newy);
context.rotate(-Math.PI / 2);
context.textAlign = "center";
context.fillText("Your Label Here", labelXposition, 0);
context.restore();

textAlign 是横向对齐,再根据标准找到了一个纵向对齐 textBaseline

void function() {
  // ...
  context.textAlign = 'center'; // <<<<<<< insert
  context.textBaseline = 'middle'; // <<<<<<< insert
  var x = 100;
  var y = 100;
  var angle = 1 / 8 * Math.PI;
  context.translate(x, y);
  context.rotate(angle);
  context.strokeText('A', 0, 0);
  // ...
}()

 

修改以后,效果符合预期,见下图:

示例2

按我的习惯就这种 “常用” 功能就封装成独立函数,方便以后使用。

/**
 * 绘制旋转的文字
 * @param {CanvasRenderingContext2D} context 上下文
 * @param {String} text 文本
 * @param {Number} x 中心坐标 x
 * @param {Number} y 中心坐标 y
 * @param {Number} angle 角度,单位弧度
 */
function rotateText(context, text, x, y, angle) {
  if (!context) {
    return;
  }

  context.save(); // 保存上次的风格设置
  context.textAlign = 'center'; // 横向居中
  context.textBaseline = 'middle'; // 纵向居中
  context.translate(x, y); // 修改坐标系原点
  context.rotate(angle); // 旋转
  context.strokeText(text, 0, 0); // 绘制文本
  context.restore(); // 恢复上次的风格设置
}

 

如何计算每个字符出现位置和角度?

背景文字左右平移 + 旋转,生成随机的字符串计算中心坐标就好了

前景文字基本相似,只要上下来回移动和稍微摇摆,这里用的 cos 曲线控制摇摆。

 

如何生成 gif 图片

生成 gif 有第三方库可以使用 gifjs。 这里要注意的是,gifjs 用到 worker 技术,所以得在 http:// 环境里调试,不能用 file:// 环境

 

注意:由于添加的是同一个 canvas 对象,所以的是使用 copy 模式,将图像数据保留给每一帧。

gif.addFrame(canvasTemp, { delay: 100, copy: true });

 

完整代码

<!doctype html>
<html>
<head>
  <meta charset="utf-8" />
  <style>
canvas {
  border: black 1px solid;
}
  </style>
  <script src="../library/gif.js"></script>
</head>
<body>
  <div>
    Key: <input type="text" maxlength="8" /> <input type="button" value="build" />
  </div>
  <canvas width="300" height="70"></canvas>
  <img width="300" height="70" /><a download="captcha.gif">download...</a>
  <script>

/**
 * 绘制旋转的文字
 * @param {CanvasRenderingContext2D} context 上下文
 * @param {String} text 文本
 * @param {Number} x 中心坐标 x
 * @param {Number} y 中心坐标 y
 * @param {Number} angle 角度,单位弧度
 */
function rotateText(context, text, x, y, angle) {
  if (!context) {
    return;
  }

  context.save(); // 保存上次的风格设置
  context.textAlign = 'center'; // 横向居中
  context.textBaseline = 'middle'; // 纵向居中
  context.translate(x, y); // 修改坐标系原点
  context.rotate(angle); // 旋转
  context.strokeText(text, 0, 0); // 绘制文本
  context.restore(); // 恢复上次的风格设置
}

/**
 * 随机字符串
 * @param{String} chars 字符串
 * @param{Number} len 长度
 */
function randomText(chars, len) {
  var result = '';
  for (var i = 0; i < len; i++) {
    result += chars.charAt(parseInt(chars.length * Math.random()));
  }
  return result;
}

void function() {
  // @see http://www.w3.org/TR/2dcontext/
  var canvas = document.querySelector('canvas');
  var context = canvas.getContext('2d');

  context.font = '30px Verdana'; // 字体大小和字体名

  var lineHeight = 15; // 行高
  var backLength = 3;
  var backTexts = {};
  var backXOffsets = {};
  var keyYOffsets = {};
  var keyAOffsets = {};
  var backSpeed = 10000 + parseInt(100 * Math.random());
  var keySpeed = 12000 + parseInt(100 * Math.random());
  var key = '';

  function init(value) {
    key = String(value).toUpperCase();
    // 随机备件
    for (var i = 0; i < canvas.height / lineHeight; i++) {
      backTexts[i] = randomText('ABCDEFGHIJKLMNOPQRST0123456789', backLength);
      backXOffsets[i] = Math.random() * canvas.width;
    }
    for (var i = 0; i < key.length; i++) {
      keyYOffsets[i] = Math.random() * lineHeight / 2;
      keyAOffsets[i] = 0.05 - Math.random() * 0.1;
    }
  }

  function renderBack(now, context, text, y, xOffset) {
    var tick = now % backSpeed;
    for (var i = 0; i < backLength; i++) {
      var t = (xOffset + (tick / backSpeed) * canvas.width + 
        (canvas.width / backLength) * i) % canvas.width;
      rotateText(context, text[i], t, y,
          i / backLength * Math.PI * 2 + (tick / backSpeed) * Math.PI * 2);
    }
  }

  function render(now, context) {
    context.fillStyle = '#FFFFFF';
    context.fillRect(0, 0, canvas.width, canvas.height);
    context.fillStyle = '#000000';

    // 绘制背景文字
    for (var i = 0; i < canvas.height / lineHeight; i++) {
      renderBack(now, context, backTexts[i], lineHeight * i, backXOffsets[i]);
    }

    // 绘制 key
    var tick = now % keySpeed;
    var keyCharWidth = canvas.width / key.length;
    for (var i = 0; i < key.length; i++) {
      var tx = keyCharWidth + (((canvas.width - keyCharWidth) / key.length) * i) % canvas.width;
      var ty = Math.cos(now / 1000) * Math.PI * keyYOffsets[i];
      rotateText(context, key[i], tx,
        canvas.height / 2 - ty, Math.cos(now / 1000) * Math.PI * 0.1 + keyAOffsets[i]);
    }
  }
  init('zswang');
  setInterval(function() {
    render(Number(new Date), context);
  }, 100);


  document.querySelector('input[type=text]').addEventListener('input', function() {
    init(this.value);
  });

  document.querySelector('input[type=button]').addEventListener('click', function() {
    var self = this;
    self.disabled = true;
    var gif = new GIF({
      repeat: 0,
      workers: 2,
      quality: 10,
      workerScript: '../library/gif.worker.js'
    });

    // 生成 gif 图片
    var canvasTemp = document.createElement('canvas');
    canvasTemp.width = canvas.width;
    canvasTemp.height = canvas.height;
    var context = canvasTemp.getContext('2d');
    context.font = '30px Verdana'; // 字体大小和字体名
    context.textAlign = 'center';
    for (var i = 0; i < 5000; i += 100) {
      render(i, context);
      gif.addFrame(canvasTemp, { delay: 100, copy: true });
    }
    gif.on('finished', function(blob) {
      var url = URL.createObjectURL(blob);
      document.querySelector('img').src = url;
      document.querySelector('a').href = url;
      self.disabled = false;
    });
    gif.render();
  });
}();
  </script>
</body>
</html>

 

后记

功能比较简单,也写得比较简单,仅供参考。如果要应用到实战,还有很多细节要考虑

  • gif 创建的过程必然得放到后端完成,否则 兼容性、性能、安全性 都是问题(这块和传统的验证过程并无区别)。
  • 缓存(背景效果可以重复利用一段时间)。
  • 图片大小需要优化,目前是 200K(通过调整帧率和压缩比)。
  • 提供方便的调用接口(模块化)。

 

参考资料

 

作者:zswang (http://weibo.com/zswang) - 站在巨人的肩上也要成为巨人的一部分

 

分享到:
评论

相关推荐

    Java实现点击文字验证码与拖动/滑动图片验证码(源码+demo+单元测试+实现思路)

    1.部署步骤 ...4.实现思路 4.1点击文字图片验证码:https://blog.csdn.net/m0_38138879/article/details/117552010 4.2拖动图片验证码:https://blog.csdn.net/m0_38138879/article/details/117559456

    基于python实现破解滑动验证码过程解析

    前言: 很多小伙伴们反馈,在web自动化的过程中,经常会被...关于滑动验证码破解的思路大体上来讲就是以下两个步骤: 1、获取滑块滑动的距离 2、模拟拖动滑块,通过验证。 听起来是比较简单,但是获取滑块滑动的距离,

    SpringBoot实现短信验证码校验方法思路详解

    最近做项目遇到这样的需求,前端是基于BootStrap,html代码中有BootStrap样式实现的,具体后台实现代码大家通过本文一起学习吧

    100%成功率的顶象面积验证码识别思路.zip

    100%成功率的顶象面积验证码识别思路.zip100%成功率的顶象面积验证码识别思路.zip100%成功率的顶象面积验证码识别思路.zip100%成功率的顶象面积验证码识别思路.zip100%成功率的顶象面积验证码识别思路.zip100%成功率...

    可以选择个数的正方形验证码输入框实现的源码

    本资源为 博客 Android自定义view 实现 可选个数的正方形验证码输入框的的源码 实现思路 非常简洁。代码可拓展性很强 耦合度很低

    vue实现短信验证码登录功能(流程详解)

    思路 1,先判断手机号和验证是否为空, 2,点击发送验证码,得到验证码 3,输入的验证码是否为空和是否正确, 4,最后向服务发送请求 界面展示 1.准备工作 这个会对input进行封装处理 &lt;!-- 输入框 --&gt;...

    基于struts1.x的验证码实现

    基于struts1.x的验证码获取实现,具体验证部分可由自己加入,思路已附在说明里面。

    ios-iOS 验证码输入一种实现思路.zip

    简书: https://www.jianshu.com/p/23f7be3677be GitHub: https://github.com/HouWan/CodeTextDemo

    胜朝科技验证码识别系统 演示版

    都可在一定程度上实现验证码的作用,但综合考虑实现的成本、用户体验、破解的难度这些因素,目前使用最广泛的是看图识字这种形式,即图形验证码,就是根据图片上的数字、字母,用户再重新输入一遍,这种方式实现较为...

    CV万能验证码识别填写软件 v2019

    都可在一定程度上实现验证码的作用,但综合考虑实现的成本、用户体验、破解的难度这些因素,目前使用最广泛的是看图识字这种形式,即图形验证码,就是根据图片上的数字、字母,用户再重新输入一遍,这种方式实现较为...

    IOS实现输入验证码、密码按位分割(二)

    本文提供了实现IOS实现输入验证码、密码按位分割的一种思路,分享给大家供大家参考,希望与大家共同交流。 一、实现思路 1、思路描述 自定义一个view,继承自UIView 在view中添加子控件textField,background...

    Android实现短信验证码自动填写功能

    实现思路很简单: 1、在需要输入验证码的Activity代码注册监听短信的广播 2、拦截短信,获取其中的验证码 3、回写到EditText private SmsReciver smsReciver = new SmsReciver(); /** 收到短信Action **/ ...

    uniapp自定义验证码输入框,隐藏光标

    实现思路就是使用for循环去创建5个正方形的view标签,然后创建一个input标签,type=tel,最大输入长度为5(根据需求来设置),再将input伪隐藏掉,获取的值分别放到5个view中展示。 验证码失败后利用v-model双向绑定...

    CV全自动图形图片验证码识别软件

    都可在一定程度上实现验证码的作用,但综合考虑实现的成本、用户体验、破解的难度这些因素,目前使用最广泛的是看图识字这种形式,即图形验证码,就是根据图片上的数字、字母,用户再重新输入一遍,这种方式实现较为...

    vue3.0实现点击切换验证码(组件)及校验

    本文实例为大家分享了vue3.0实现点击切换验证码(组件)及校验的具体代码,供大家参考,具体内容如下 先看效果 父组件 验证码 placeholder=输入验证码 v-model=verify &gt; 点击更换

    ASP验证码组件ShotGraph

    首先,我们先介绍一下设计思路,数字和字母的随机组合生成验证码,然后将验证码生成图片,这里“数字和字母的组合”应该是随机取出来的;如果是专门的数字验证码,我们可以这样实现: ycodenum=4 '验证码的位数...

    .Net Core 实现图片验证码的实现示例

    思路很简单=》 生成一个随机数-》保存到服务端Session-》生成随机码对应的图片给前端-》登录的时候进行校验(也可以在后端进行随机码的token加密,存到Cooick里面在前端进行校验) 第一步:生成随机数 private ...

    信用:springboot + vue实现的短信验证码demo

    springdatajpa + vue.js + axios + activemq + redis整合的第三方平台阿里大于短信平台项目描述这是一个使用阿里大于短信平台接口的登录demo,意在让大家熟悉短信验证码登录实现的基本思路进度2020/4/6项目基础环境...

    50%识别率的验证码识别程序【源代码】【C#代码】

    本资源是来自实践项目,是项目中的验证码识别学习程序。...以及验证码识别的整体思路和完整的实现方法。包括机器学习和机器自识别。 本源代码使用GPL授权协议。(不适合已经有经验的图形图像处理人员,适合新手。)

    php验证码的制作思路和实现方法

    主要介绍了php验证码的制作思路和实现方法,我们不能盲目的去实现php生成验证码,更应该了解php验证码的基本原理,真正的掌握php验证码的实现方法,需要的朋友可以参考下

Global site tag (gtag.js) - Google Analytics