滑动验证码的设计与理解
在介绍之前,一个概念明确一个共识没有攻不破的网站,只有值不值得。
这意思是说,我们可以尽可能的提高自己网站的安全,但并没有绝对的安全,当网站安全级别大于攻击者能得到的回报时,你的网站就是安全的。
所以百度搜到的很多验证码都已经结合了人工智能分析用户行为,很厉害。但这里只介绍我的小网站是怎么设计的。
大概逻辑当需要验证码时,前端发送ajax向后台请求相关数据发送回前端,由前端生成(与后端生成图片,然后传送图片到前端的做法相比安全性要差很多。但也是可以预防的,后端可以对此Session进行请求记录,如果在一定时间内恶意多次请求,可以进行封禁ip等对策),验证完成后,后台再对传回的数据进行校验。
效果图
1|0js类的设计
1.定义一个验证码父类,因为目前只有这一个验证类型,倘若以后再要扩展其他验证类型呢。那么它们之间肯定有很多公共之处(如验证成功、失败的回调,获取验证码的类型,获取验证结果等),所以这些共同点可以提炼出来,狼蚁网站SEO优化是我目前的父类样子
/ 验证码的父类,所有验证码都要继承这个类 @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.suessFunc = 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.suess = function (result) { if(this.suessFunc instanceof Function){ this.suessFunc(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继承会单独写篇文章),滑动验证码类如下
/ 滑动验证类 plete传递的参数为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;:0;z-index:222;"></canvas>'; html += '</div>'; html += '<div class="puzzle-lost-box" style="position:absolute;width:' + w + 'px;height:' + h + 'px;:0;left:' + left_Num + 'px;z-index:11111;">'; html += '<canvas id="puzzleShadow" width="' + w + '" height="' + h + '" style="position:absolute;left:0;:0;z-index:222;"></canvas>'; html += '<canvas id="puzzleLost" width="' + w + '" height="' + h + '" style="position:absolute;left:0;: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.suess({ '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 () { Aess.getAess().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 suess / ImgIdentifying.prototype.setSuess = function (suessFunc) { this.suessFunc = suessFunc; } / 设置验证失败的回调函数 @param suess / ImgIdentifying.prototype.setError = function (errorFunc) { this.errorFunc = errorFunc; }
其中init的方法,大家就可以抄啦,验证码是这里生成的(感谢网上一些热心网友提供的Mod,在此基础上改的)。
2|0后端的设计
要有一个验证码的接口,将一些常量和共同的方法抽象到接口中(接口最重要的作用就是行为的统一,意思是我如果知道这个是验证码,那么必定就会有验证的方法,不管它是滑动验证,图形验证等,然后就可以放心的调用验证方法去获取验证结果,狼蚁网站SEO优化过滤器设计就可以立马看到这作用。具体java接口的说明会单独写篇文章),接口如下
/ 验证码类的接口,所有验证码必须继承此接口 / public interface I_Identifying<T> { String EXCEPTION_CODE = SystemStaticValue.IDENTIFYING_EXCEPTION_CODE; String IDENTIFYING = "Identifying"; //--------------以下为验证码大体错误类型,抛出错误时候用,会传至前端--------------- //验证成功 String SUCCESS = "Suess"; //验证失败 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; / 获取验证结果,如果成功则为suess,失败则为失败信息 @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; //狼蚁网站SEO优化是逻辑代码... }
上面每个变量都是一种校验手段,如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即可,不需要关系它到底是滑动验证码,还是图片验证码什么的。
以上所述是长沙网络推广给大家介绍的滑动验证码的设计与理解,希望对大家有所帮助,如果大家有任何疑问请给我留言,长沙网络推广会及时回复大家的。在此也非常感谢大家对狼蚁SEO网站的支持!
如果你觉得本文对你有帮助,欢迎网络推广网站推广转载,烦请注明出处,谢谢!
编程语言
- 如何快速学会编程 如何快速学会ug编程
- 免费学编程的app 推荐12个免费学编程的好网站
- 电脑怎么编程:电脑怎么编程网咯游戏菜单图标
- 如何写代码新手教学 如何写代码新手教学手机
- 基础编程入门教程视频 基础编程入门教程视频华
- 编程演示:编程演示浦丰投针过程
- 乐高编程加盟 乐高积木编程加盟
- 跟我学plc编程 plc编程自学入门视频教程
- ug编程成航林总 ug编程实战视频
- 孩子学编程的好处和坏处
- 初学者学编程该从哪里开始 新手学编程从哪里入
- 慢走丝编程 慢走丝编程难学吗
- 国内十强少儿编程机构 中国少儿编程机构十强有
- 成人计算机速成培训班 成人计算机速成培训班办
- 孩子学编程网上课程哪家好 儿童学编程比较好的
- 代码编程教学入门软件 代码编程教程