• 企业400电话
  • 微网小程序
  • AI电话机器人
  • 电商代运营
  • 全 部 栏 目

    企业400电话 网络优化推广 AI电话机器人 呼叫中心 网站建设 商标✡知产 微网小程序 电商运营 彩铃•短信 增值拓展业务
    滑动验证码的设计与理解

    在介绍之前,首先一个概念明确一个共识:没有攻不破的网站,只有值不值得。

    这意思是说,我们可以尽可能的提高自己网站的安全,但并没有绝对的安全,当网站安全级别大于攻击者能得到的回报时,你的网站就是安全的。

    所以百度搜到的很多验证码都已经结合了人工智能分析用户行为,很厉害。但这里只介绍我的小网站是怎么设计的。

    大概逻辑:当需要验证码时,前端发送ajax向后台请求相关数据发送回前端,由前端生成(与后端生成图片,然后传送图片到前端的做法相比安全性要差很多。但也是可以预防的,后端可以对此Session进行请求记录,如果在一定时间内恶意多次请求,可以进行封禁ip等对策),验证完成后,后台再对传回的数据进行校验。

    效果图:

    1|0js类的设计:

    1.定义一个验证码父类,因为目前只有这一个验证类型,倘若以后再要扩展其他验证类型呢。那么它们之间肯定有很多公共之处(如:验证成功、失败的回调,获取验证码的类型,获取验证结果等),所以这些共同点可以提炼出来,下面是我目前的父类样子:

     

     /**
     * 验证码的父类,所有验证码都要继承这个类
     * @param id 验证码的唯一标识
     * @param type 验证码的类型
     * @param contentDiv 包含着验证码的DIV
     * @constructor
     */
     var Identifying = function (id,type,contentDiv){
      this.id = id;
      this.type = type;
      this.contentDiv=contentDiv;
     }
     /**
     * 销毁函数
     */
     Identifying.prototype.destroy = function(){
      this.successFunc = null;
      this.errorFunc = null;
      this.clearDom();
      this.contentDiv = null;
     }
     /**
     * 清除节点内容
     */
     Identifying.prototype.clearDom = function(){
      if(this.contentDiv instanceof jQuery){
       this.contentDiv.empty();
      }else if(this.contentDiv instanceof HTMLElement){
       this.contentDiv.innerText = "";
      }
     }
     /**
     * 回调函数
     * 验证成功后进行调用
     * this需要指具体验证类
     * @param result 对象,有对应验证类的传递的参数,具体要看验证类
     */
     Identifying.prototype.success = function (result) {
      if(this.successFunc instanceof Function){
       this.successFunc(result);
      }
     }
     /**
     * 验证失败发生错误调用的函数
     * @param result
     */
     Identifying.prototype.error = function (result) {
      if(this.errorFunc instanceof Function){
       this.errorFunc(result);
      }else{
       //统一处理错误
      }
     }
     /**
     * 获取验证码id
     */
     Identifying.prototype.getId = function () {
      return this.id;
     }
     /**
     * 获取验证码类型
     * @returns {*}
     */
     Identifying.prototype.getType = function () {
      return this.type;
     }
     /**
     * 显示验证框
     */
     Identifying.prototype.showIdentifying = function(callback){
      this.contentDiv.show(null,callback);
     }
     /**
     * 隐藏验证框
     */
     Identifying.prototype.hiddenIdentifying = function(callback){
      this.contentDiv.hide(null,callback);
     }
     /**
     * 获得验证码显示的dom元素
     */
     Identifying.prototype.getContentDiv = function () {
      return this.contentDiv;
     }

    然后,滑动验证码类继承此父类(js继承会单独写篇文章),滑动验证码类如下:

      

     /**
     * 滑动验证类
     * complete传递的参数为identifyingId,identifyingType,moveEnd_X
     * @param config 各种配置
     */
     var ImgIdentifying = function(config) {
      Identifying.call(this, config.identifyingId, config.identifyingType,config.el);
      this.config = config;
      this.init();
      this.showIdentifying();
     }
     //继承父类
     extendClass(Identifying, ImgIdentifying);
     /**
     * 销毁函数
     */
     ImgIdentifying.prototype.destroy = function () {
      Identifying.prototype.destroy.call(this);
     }
     var width = '260';
     var height = '116';
     var pl_size = 48;
     var padding_ = 20;
     ImgIdentifying.prototype.init = function () {
      this.clearDom();
      var el = this.getContentDiv();
      var w = width;
      var h = height;
      var PL_Size = pl_size;
      var padding = padding_;
      var self = this;
      //这个要转移到后台
      function RandomNum(Min, Max) {
       var Range = Max - Min;
       var Rand = Math.random();
       if (Math.round(Rand * Range) == 0) {
        return Min + 1;
       } else if (Math.round(Rand * Max) == Max) {
        return Max - 1;
       } else {
        var num = Min + Math.round(Rand * Range) - 1;
        return num;
       }
      }
      //确定图片
      var imgSrc = this.config.img;
      var X = this.config.X;
      var Y = this.config.Y;
      var left_Num = -X + 10;
      var html = 'div style="position:relative;padding:16px 16px 28px;border:1px solid #ddd;background:#f2ece1;border-radius:16px;">';
      html += 'div style="position:relative;overflow:hidden;width:' + w + 'px;">';
      html += 'div style="position:relative;width:' + w + 'px;height:' + h + 'px;">';
      html += 'img id="scream" src="' + imgSrc + '" style="width:' + w + 'px;height:' + h + 'px;">';
      html += 'canvas id="puzzleBox" width="' + w + '" height="' + h + '" style="position:absolute;left:0;top:0;z-index:222;">/canvas>';
      html += '/div>';
      html += 'div class="puzzle-lost-box" style="position:absolute;width:' + w + 'px;height:' + h + 'px;top:0;left:' + left_Num + 'px;z-index:11111;">';
      html += 'canvas id="puzzleShadow" width="' + w + '" height="' + h + '" style="position:absolute;left:0;top:0;z-index:222;">/canvas>';
      html += 'canvas id="puzzleLost" width="' + w + '" height="' + h + '" style="position:absolute;left:0;top:0;z-index:333;">/canvas>';
      html += '/div>';
      html += 'p class="ver-tips">/p>';
      html += '/div>';
      html += 'div class="re-btn">a>/a>/div>';
      html += '/div>';
      html += 'br>';
      html += 'div style="position:relative;width:' + w + 'px;margin:auto;">';
      html += 'div style="border:1px solid #c3c3c3;border-radius:24px;background:#ece4dd;box-shadow:0 1px 1px rgba(12,10,10,0.2) inset;">';//inset 为内阴影
      html += 'p style="font-size:12px;color: #486c80;line-height:28px;margin:0;text-align:right;padding-right:22px;">按住左边滑块,拖动完成上方拼图/p>';
      html += '/div>';
      html += 'div class="slider-btn">/div>';
      html += '/div>';
      el.html(html);
      var d = PL_Size / 3;
      var c = document.getElementById("puzzleBox");
      //getContext获取该dom节点的canvas画布元素
      //---------------------------------这一块是图片中央缺失的那一块--------------------------------------
      var ctx = c.getContext("2d");
      ctx.globalCompositeOperation = "xor";
      //设置阴影模糊级别
      ctx.shadowBlur = 10;
      //设置阴影的颜色
      ctx.shadowColor = "#fff";
      //设置阴影距离的水平距离
      ctx.shadowOffsetX = 3;
      //设置阴影距离的垂直距离
      ctx.shadowOffsetY = 3;
      //rgba第四个参数是透明度,前三个是三原色,跟rgb比就是多了第四个参数
      ctx.fillStyle = "rgba(0,0,0,0.8)";
      //beginPath() 方法开始一条路径,或重置当前的路径。
      //提示:请使用这些方法来创建路径:moveTo()、lineTo()、quadricCurveTo()、bezierCurveTo()、arcTo() 以及 arc()。
      ctx.beginPath();
      //指线条的宽度
      ctx.lineWidth = "1";
      //strokeStyle 属性设置或返回用于笔触的颜色、渐变或模式
      ctx.strokeStyle = "rgba(0,0,0,0)";
      //表示画笔移到(X,Y)位置,没画东西
      ctx.moveTo(X, Y);
      //画笔才开始移动到指定坐标,之间画一条直线
      ctx.lineTo(X + d, Y);
      //绘制一条贝塞尔曲线,一共四个点确定,开始点(没在参数里),和两个控制点(1和2参数结合,3和4参数结合),结束点(5和6参数结合)
      ctx.bezierCurveTo(X + d, Y - d, X + 2 * d, Y - d, X + 2 * d, Y);
      ctx.lineTo(X + 3 * d, Y);
      ctx.lineTo(X + 3 * d, Y + d);
      ctx.bezierCurveTo(X + 2 * d, Y + d, X + 2 * d, Y + 2 * d, X + 3 * d, Y + 2 * d);
      ctx.lineTo(X + 3 * d, Y + 3 * d);
      ctx.lineTo(X, Y + 3 * d);
      //必须和beginPath()成对出现
      ctx.closePath();
      //进行绘制
      ctx.stroke();
      //根据fillStyle进行填充
      ctx.fill();
      //---------------------------------这个为要移动的块------------------------------------------------
      var c_l = document.getElementById("puzzleLost");
      //---------------------------------这个为要移动的块增加阴影------------------------------------------------
      var c_s = document.getElementById("puzzleShadow");
      var ctx_l = c_l.getContext("2d");
      var ctx_s = c_s.getContext("2d");
      var img = new Image();
      img.src = imgSrc;
      img.onload = function () {
       //从原图片,进行设置处理再显示出来(其实就是设置你想显示图片的位置2和3参数,和框w高h)
       ctx_l.drawImage(img, 0, 0, w, h);
      }
      ctx_l.beginPath();
      ctx_l.strokeStyle = "rgba(0,0,0,0)";
      ctx_l.moveTo(X, Y);
      ctx_l.lineTo(X + d, Y);
      ctx_l.bezierCurveTo(X + d, Y - d, X + 2 * d, Y - d, X + 2 * d, Y);
      ctx_l.lineTo(X + 3 * d, Y);
      ctx_l.lineTo(X + 3 * d, Y + d);
      ctx_l.bezierCurveTo(X + 2 * d, Y + d, X + 2 * d, Y + 2 * d, X + 3 * d, Y + 2 * d);
      ctx_l.lineTo(X + 3 * d, Y + 3 * d);
      ctx_l.lineTo(X, Y + 3 * d);
      ctx_l.closePath();
      ctx_l.stroke();
      //带阴影,数字越高阴影越严重
      ctx_l.shadowBlur = 10;
      //阴影的颜色
      ctx_l.shadowColor = "black";
      // ctx_l.fill(); 其实加这句就能有阴影效果了,不知道为什么加多个图层
      //分割画布的块
      ctx_l.clip();
      ctx_s.beginPath();
      ctx_s.lineWidth = "1";
      ctx_s.strokeStyle = "rgba(0,0,0,0)";
      ctx_s.moveTo(X, Y);
      ctx_s.lineTo(X + d, Y);
      ctx_s.bezierCurveTo(X + d, Y - d, X + 2 * d, Y - d, X + 2 * d, Y);
      ctx_s.lineTo(X + 3 * d, Y);
      ctx_s.lineTo(X + 3 * d, Y + d);
      ctx_s.bezierCurveTo(X + 2 * d, Y + d, X + 2 * d, Y + 2 * d, X + 3 * d, Y + 2 * d);
      ctx_s.lineTo(X + 3 * d, Y + 3 * d);
      ctx_s.lineTo(X, Y + 3 * d);
      ctx_s.closePath();
      ctx_s.stroke();
      ctx_s.shadowBlur = 20;
      ctx_s.shadowColor = "black";
      ctx_s.fill();
      //开始时间
      var beginTime;
      //结束时间
      var endTime;
      var moveStart = '';
      $(".slider-btn").mousedown(function (e) {
       $(this).css({"background-position": "0 -216px"});
       moveStart = e.pageX;
       beginTime = new Date().valueOf();
      });
      onmousemove = function (e) {
       var e = e || window.event;
       var moveX = e.pageX;
       var d = moveX - moveStart;
       if (moveStart == '') {
       } else {
        if (d  0 || d > (w - padding - PL_Size)) {
        } else {
         $(".slider-btn").css({"left": d + 'px', "transition": "inherit"});
         $("#puzzleLost").css({"left": d + 'px', "transition": "inherit"});
         $("#puzzleShadow").css({"left": d + 'px', "transition": "inherit"});
        }
       }
      };
      onmouseup = function (e) {
       var e = e || window.event;
       var moveEnd_X = e.pageX - moveStart;
       var ver_Num = X - 10;
       var deviation = self.config.deviation;
       var Min_left = ver_Num - deviation;
       var Max_left = ver_Num + deviation;
       if (moveStart == '') {
       } else {
        endTime = new Date().valueOf();
        if (Max_left > moveEnd_X  moveEnd_X > Min_left) {
         $(".ver-tips").html('i style="background-position:-4px -1207px;">/i>span style="color:#42ca6b;">验证通过/span>span>/span>');
         $(".ver-tips").addClass("slider-tips");
         $(".puzzle-lost-box").addClass("hidden");
         $("#puzzleBox").addClass("hidden");
         setTimeout(function () {
          $(".ver-tips").removeClass("slider-tips");
         }, 2000);
         self.success({
          'identifyingId': self.config.identifyingId, 'identifyingType': self.config.identifyingType,
          'moveEnd_X': moveEnd_X
         })
        } else {
         $(".ver-tips").html('i style="background-position:-4px -1229px;">/i>span style="color:red;">验证失败:/span>span style="margin-left:4px;">拖动滑块将悬浮图像正确拼合/span>');
         $(".ver-tips").addClass("slider-tips");
         setTimeout(function () {
          $(".ver-tips").removeClass("slider-tips");
         }, 2000);
         self.error();
        }
       }
       //0.5指动画执行到结束一共经历的时间
       setTimeout(function () {
        $(".slider-btn").css({"left": '0', "transition": "left 0.5s"});
        $("#puzzleLost").css({"left": '0', "transition": "left 0.5s"});
        $("#puzzleShadow").css({"left": '0', "transition": "left 0.5s"});
       }, 1000);
       $(".slider-btn").css({"background-position": "0 -84px"});
       moveStart = '';
       $(".re-btn a").on("click", function () {
        Access.getAccess().initIdentifying($('#acessIdentifyingContent'));
       })
      }
     }
     /**
     * 获取该类型验证码的一些参数
     */
     ImgIdentifying.getParamMap = function () {
      var min_X = padding_ + pl_size;
      var max_X = width - padding_ - pl_size - pl_size / 6;
      var max_Y = padding_;
      var min_Y = height - padding_ - pl_size - pl_size / 6;
      var paramMap = new Map();
      paramMap.set("min_X", min_X);
      paramMap.set("max_X", max_X);
      paramMap.set("min_Y", min_Y);
      paramMap.set("max_Y", max_Y);
      return paramMap;
     }
     /**
     * 设置验证成功的回调函数
     * @param success
     */
     ImgIdentifying.prototype.setSuccess = function (successFunc) {
      this.successFunc = successFunc;
     }
     /**
     * 设置验证失败的回调函数
     * @param success
     */
     ImgIdentifying.prototype.setError = function (errorFunc) {
      this.errorFunc = errorFunc;
     }

    其中init的方法,大家就可以抄啦,验证码是这里生成的(感谢网上一些热心网友提供的Mod,在此基础上改的)。

    2|0后端的设计:

    首先要有一个验证码的接口,将一些常量和共同的方法抽象到接口中(接口最重要的作用就是行为的统一,意思是我如果知道这个是验证码,那么必定就会有验证的方法,不管它是滑动验证,图形验证等,然后就可以放心的调用验证方法去获取验证结果,下面过滤器设计就可以立马看到这作用。具体java接口的说明会单独写篇文章),接口如下:

     /**
     * 验证码类的接口,所有验证码必须继承此接口
     */
     public interface I_IdentifyingT> {
      String EXCEPTION_CODE = SystemStaticValue.IDENTIFYING_EXCEPTION_CODE;
      String IDENTIFYING = "Identifying";
      //--------------以下为验证码大体错误类型,抛出错误时候用,会传至前端---------------
      //验证成功
      String SUCCESS = "Success";
      //验证失败
      String FAILURE = "Failure";
      //验证码过期
      String OVERDUE = "Overdue";
      //-------以下为验证码具体错误类型,存放在checkResult-------------
      String PARAM_ERROR = "验证码参数错误";
      String OVERDUE_ERROR = "验证码过期";
      String TYPE_ERROR = "验证码业务类型错误";
      String ID_ERROR = "验证码id异常";
      String CHECK_ERROR = "验证码验证异常";
      /**
      * 获取生成好的验证码
      * @param request
      * @return
      */
      public T getInstance(HttpServletRequest request) throws Exception;
      /**
      * 进行验证,没抛异常说明验证无误
      * @return
      */
      public void checkIdentifying(HttpServletRequest request) throws Exception;
      /**
      * 获取验证结果,如果成功则为success,失败则为失败信息
      * @return
      */
      public String getCheckResult();
      /**
      * 获取验证码的业务类型
      * @return
      */
      public String getIdentifyingType();
     }

    然后,设计一个具体的滑动验证类去实现这个接口,这里只贴参数:

     /**
     * @author NiceBin
     * @description: 验证码类,前端需要生成验证码的信息
     * @date 2019/7/12 16:04
     */
     public class ImgIdentifying implements I_IdentifyingImgIdentifying>,Serializable {
      //此次验证码的id
      private String identifyingId;
      //此次验证码的业务类型
      private String identifyingType;
      //需要使用的图片
      private String imgSrc;
      //生成块的x坐标
      private int X;
      //生成块的y坐标
      private int Y;
      //允许的误差
      private int deviation = 2;
      //验证码生成的时间
      private Calendar calendar;
      //验证码结果,如果有结果说明已经被校验,防止因为网络延时的二次校验
      private String checkResult;
     
      //下面是逻辑代码...
     }

    上面每个变量都是一种校验手段,如calendar可以检验验证码是否过期,identifyingType检验此验证码是否是对应的业务等。每多想一点,别人破解就多费劲一点。

    后端验证码的验证是不需要具体的类去调用的,而是被一个过滤器统一过滤,才过滤器注册的时候,将需要进行验证的路径写进去即可,过滤器代码如下:

    r NiceBin
     * @description: 验证码过滤器,帮忙验证有需要验证码的请求,不帮忙生成验证码
     * @date 2019/7/23 15:06
     */
     @Component
     public class IdentifyingInterceptor implements HandlerInterceptor {
      @Override
      public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
       HttpSession session = request.getSession();
       I_Identifying identifying= (I_Identifying)session.getAttribute(I_Identifying.IDENTIFYING);
       if(identifying!=null){
        identifying.checkIdentifying(request);
       }else {
        //应该携带验证码信息的,结果没有携带,那就是个非法请求
        return false;
       }
       return true;
      }
      @Override
      public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
      }
      @Override
      public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
      }
     }

    可以看到接口的用处了,之前在用户申请验证码时,验证码类是放到用户session中的,所以这里直接取出调用checkIdentifying即可,不需要关系它到底是滑动验证码,还是图片验证码什么的。

    总结

    以上所述是小编给大家介绍的滑动验证码的设计与理解,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对脚本之家网站的支持!
    如果你觉得本文对你有帮助,欢迎转载,烦请注明出处,谢谢!

    您可能感兴趣的文章:
    • js+canvas实现滑动拼图验证码功能
    • 使用puppeteer破解极验的滑动验证码
    • Java实现滑动验证码的示例代码
    • selenium+java破解极验滑动验证码的示例代码
    • 使用 Node.js 模拟滑动拼图验证码操作的示例代码
    • js插件实现图片滑动验证码
    • Java selenium处理极验滑动验证码示例
    上一篇:delphi使用Chilkat 组件和库从SFTP下载文件的方法
    下一篇:Delphi 本地路径的创建、清空本地指定文件夹下的文件
  • 相关文章
  • 

    © 2016-2020 巨人网络通讯 版权所有

    《增值电信业务经营许可证》 苏ICP备15040257号-8

    滑动验证码的设计与理解 滑动,验证,码,的,设计,与,