侯体宗的博客
  • 首页
  • Hyperf版
  • beego仿版
  • 人生(杂谈)
  • 技术
  • 关于我
  • 更多分类
    • 文件下载
    • 文字修仙
    • 中国象棋ai
    • 群聊
    • 九宫格抽奖
    • 拼图
    • 消消乐
    • 相册

滑动验证码的设计与理解

技术  /  管理员 发布于 7年前   140

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

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

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

大概逻辑:当需要验证码时,前端发送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="https:/article/' + 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_Identifying<T> {  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_Identifying<ImgIdentifying>,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即可,不需要关系它到底是滑动验证码,还是图片验证码什么的。

总结

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


  • 上一条:
    Delphi 本地路径的创建、清空本地指定文件夹下的文件
    下一条:
    delphi使用Chilkat 组件和库从SFTP下载文件的方法
  • 昵称:

    邮箱:

    0条评论 (评论内容有缓存机制,请悉知!)
    最新最热
    • 分类目录
    • 人生(杂谈)
    • 技术
    • linux
    • Java
    • php
    • 框架(架构)
    • 前端
    • ThinkPHP
    • 数据库
    • 微信(小程序)
    • Laravel
    • Redis
    • Docker
    • Go
    • swoole
    • Windows
    • Python
    • 苹果(mac/ios)
    • 相关文章
    • gmail发邮件报错:534 5.7.9 Application-specific password required...解决方案(0个评论)
    • 2024.07.09日OpenAI将终止对中国等国家和地区API服务(0个评论)
    • 2024/6/9最新免费公益节点SSR/V2ray/Shadowrocket/Clash节点分享|科学上网|免费梯子(0个评论)
    • 国外服务器实现api.openai.com反代nginx配置(0个评论)
    • 2024/4/28最新免费公益节点SSR/V2ray/Shadowrocket/Clash节点分享|科学上网|免费梯子(0个评论)
    • 近期文章
    • 在go语言中使用api.geonames.org接口实现根据国际邮政编码获取地址信息功能(1个评论)
    • 在go语言中使用github.com/signintech/gopdf实现生成pdf分页文件功能(0个评论)
    • gmail发邮件报错:534 5.7.9 Application-specific password required...解决方案(0个评论)
    • 欧盟关于强迫劳动的规定的官方举报渠道及官方举报网站(0个评论)
    • 在go语言中使用github.com/signintech/gopdf实现生成pdf文件功能(0个评论)
    • Laravel从Accel获得5700万美元A轮融资(0个评论)
    • 在go + gin中gorm实现指定搜索/区间搜索分页列表功能接口实例(0个评论)
    • 在go语言中实现IP/CIDR的ip和netmask互转及IP段形式互转及ip是否存在IP/CIDR(0个评论)
    • PHP 8.4 Alpha 1现已发布!(0个评论)
    • Laravel 11.15版本发布 - Eloquent Builder中添加的泛型(0个评论)
    • 近期评论
    • 122 在

      学历:一种延缓就业设计,生活需求下的权衡之选中评论 工作几年后,报名考研了,到现在还没认真学习备考,迷茫中。作为一名北漂互联网打工人..
    • 123 在

      Clash for Windows作者删库跑路了,github已404中评论 按理说只要你在国内,所有的流量进出都在监控范围内,不管你怎么隐藏也没用,想搞你分..
    • 原梓番博客 在

      在Laravel框架中使用模型Model分表最简单的方法中评论 好久好久都没看友情链接申请了,今天刚看,已经添加。..
    • 博主 在

      佛跳墙vpn软件不会用?上不了网?佛跳墙vpn常见问题以及解决办法中评论 @1111老铁这个不行了,可以看看近期评论的其他文章..
    • 1111 在

      佛跳墙vpn软件不会用?上不了网?佛跳墙vpn常见问题以及解决办法中评论 网站不能打开,博主百忙中能否发个APP下载链接,佛跳墙或极光..
    • 2016-10
    • 2016-11
    • 2017-07
    • 2017-08
    • 2017-09
    • 2018-01
    • 2018-07
    • 2018-08
    • 2018-09
    • 2018-12
    • 2019-01
    • 2019-02
    • 2019-03
    • 2019-04
    • 2019-05
    • 2019-06
    • 2019-07
    • 2019-08
    • 2019-09
    • 2019-10
    • 2019-11
    • 2019-12
    • 2020-01
    • 2020-03
    • 2020-04
    • 2020-05
    • 2020-06
    • 2020-07
    • 2020-08
    • 2020-09
    • 2020-10
    • 2020-11
    • 2021-04
    • 2021-05
    • 2021-06
    • 2021-07
    • 2021-08
    • 2021-09
    • 2021-10
    • 2021-12
    • 2022-01
    • 2022-02
    • 2022-03
    • 2022-04
    • 2022-05
    • 2022-06
    • 2022-07
    • 2022-08
    • 2022-09
    • 2022-10
    • 2022-11
    • 2022-12
    • 2023-01
    • 2023-02
    • 2023-03
    • 2023-04
    • 2023-05
    • 2023-06
    • 2023-07
    • 2023-08
    • 2023-09
    • 2023-10
    • 2023-12
    • 2024-02
    • 2024-04
    • 2024-05
    • 2024-06
    • 2025-02
    Top

    Copyright·© 2019 侯体宗版权所有· 粤ICP备20027696号 PHP交流群

    侯体宗的博客