滑動(dòng)驗(yàn)證碼的設(shè)計(jì)與理解
在介紹之前,首先一個(gè)概念明確一個(gè)共識(shí):沒有攻不破的網(wǎng)站,只有值不值得。
這意思是說,我們可以盡可能的提高自己網(wǎng)站的安全,但并沒有絕對(duì)的安全,當(dāng)網(wǎng)站安全級(jí)別大于攻擊者能得到的回報(bào)時(shí),你的網(wǎng)站就是安全的。
所以百度搜到的很多驗(yàn)證碼都已經(jīng)結(jié)合了人工智能分析用戶行為,很厲害。但這里只介紹我的小網(wǎng)站是怎么設(shè)計(jì)的。
大概邏輯:當(dāng)需要驗(yàn)證碼時(shí),前端發(fā)送ajax向后臺(tái)請(qǐng)求相關(guān)數(shù)據(jù)發(fā)送回前端,由前端生成(與后端生成圖片,然后傳送圖片到前端的做法相比安全性要差很多。但也是可以預(yù)防的,后端可以對(duì)此Session進(jìn)行請(qǐng)求記錄,如果在一定時(shí)間內(nèi)惡意多次請(qǐng)求,可以進(jìn)行封禁ip等對(duì)策),驗(yàn)證完成后,后臺(tái)再對(duì)傳回的數(shù)據(jù)進(jìn)行校驗(yàn)。
效果圖:
1|0js類的設(shè)計(jì):
1.定義一個(gè)驗(yàn)證碼父類,因?yàn)槟壳爸挥羞@一個(gè)驗(yàn)證類型,倘若以后再要擴(kuò)展其他驗(yàn)證類型呢。那么它們之間肯定有很多公共之處(如:驗(yàn)證成功、失敗的回調(diào),獲取驗(yàn)證碼的類型,獲取驗(yàn)證結(jié)果等),所以這些共同點(diǎn)可以提煉出來,下面是我目前的父類樣子:
/** * 驗(yàn)證碼的父類,所有驗(yàn)證碼都要繼承這個(gè)類 * @param id 驗(yàn)證碼的唯一標(biāo)識(shí) * @param type 驗(yàn)證碼的類型 * @param contentDiv 包含著驗(yàn)證碼的DIV * @constructor */ var Identifying = function (id,type,contentDiv){ this.id = id; this.type = type; this.contentDiv=contentDiv; } /** * 銷毀函數(shù) */ Identifying.prototype.destroy = function(){ this.successFunc = null; this.errorFunc = null; this.clearDom(); this.contentDiv = null; } /** * 清除節(jié)點(diǎn)內(nèi)容 */ Identifying.prototype.clearDom = function(){ if(this.contentDiv instanceof jQuery){ this.contentDiv.empty(); }else if(this.contentDiv instanceof HTMLElement){ this.contentDiv.innerText = ""; } } /** * 回調(diào)函數(shù) * 驗(yàn)證成功后進(jìn)行調(diào)用 * this需要指具體驗(yàn)證類 * @param result 對(duì)象,有對(duì)應(yīng)驗(yàn)證類的傳遞的參數(shù),具體要看驗(yàn)證類 */ Identifying.prototype.success = function (result) { if(this.successFunc instanceof Function){ this.successFunc(result); } } /** * 驗(yàn)證失敗發(fā)生錯(cuò)誤調(diào)用的函數(shù) * @param result */ Identifying.prototype.error = function (result) { if(this.errorFunc instanceof Function){ this.errorFunc(result); }else{ //統(tǒng)一處理錯(cuò)誤 } } /** * 獲取驗(yàn)證碼id */ Identifying.prototype.getId = function () { return this.id; } /** * 獲取驗(yàn)證碼類型 * @returns {*} */ Identifying.prototype.getType = function () { return this.type; } /** * 顯示驗(yàn)證框 */ Identifying.prototype.showIdentifying = function(callback){ this.contentDiv.show(null,callback); } /** * 隱藏驗(yàn)證框 */ Identifying.prototype.hiddenIdentifying = function(callback){ this.contentDiv.hide(null,callback); } /** * 獲得驗(yàn)證碼顯示的dom元素 */ Identifying.prototype.getContentDiv = function () { return this.contentDiv; }
然后,滑動(dòng)驗(yàn)證碼類繼承此父類(js繼承會(huì)單獨(dú)寫篇文章),滑動(dòng)驗(yàn)證碼類如下:
/** * 滑動(dòng)驗(yàn)證類 * complete傳遞的參數(shù)為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); /** * 銷毀函數(shù) */ 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; //這個(gè)要轉(zhuǎn)移到后臺(tái) 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 為內(nèi)陰影 html += '<p style="font-size:12px;color: #486c80;line-height:28px;margin:0;text-align:right;padding-right:22px;">按住左邊滑塊,拖動(dòng)完成上方拼圖</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節(jié)點(diǎn)的canvas畫布元素 //---------------------------------這一塊是圖片中央缺失的那一塊-------------------------------------- var ctx = c.getContext("2d"); ctx.globalCompositeOperation = "xor"; //設(shè)置陰影模糊級(jí)別 ctx.shadowBlur = 10; //設(shè)置陰影的顏色 ctx.shadowColor = "#fff"; //設(shè)置陰影距離的水平距離 ctx.shadowOffsetX = 3; //設(shè)置陰影距離的垂直距離 ctx.shadowOffsetY = 3; //rgba第四個(gè)參數(shù)是透明度,前三個(gè)是三原色,跟rgb比就是多了第四個(gè)參數(shù) ctx.fillStyle = "rgba(0,0,0,0.8)"; //beginPath() 方法開始一條路徑,或重置當(dāng)前的路徑。 //提示:請(qǐng)使用這些方法來創(chuàng)建路徑:moveTo()、lineTo()、quadricCurveTo()、bezierCurveTo()、arcTo() 以及 arc()。 ctx.beginPath(); //指線條的寬度 ctx.lineWidth = "1"; //strokeStyle 屬性設(shè)置或返回用于筆觸的顏色、漸變或模式 ctx.strokeStyle = "rgba(0,0,0,0)"; //表示畫筆移到(X,Y)位置,沒畫東西 ctx.moveTo(X, Y); //畫筆才開始移動(dòng)到指定坐標(biāo),之間畫一條直線 ctx.lineTo(X + d, Y); //繪制一條貝塞爾曲線,一共四個(gè)點(diǎn)確定,開始點(diǎn)(沒在參數(shù)里),和兩個(gè)控制點(diǎn)(1和2參數(shù)結(jié)合,3和4參數(shù)結(jié)合),結(jié)束點(diǎn)(5和6參數(shù)結(jié)合) 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()成對(duì)出現(xiàn) ctx.closePath(); //進(jìn)行繪制 ctx.stroke(); //根據(jù)fillStyle進(jìn)行填充 ctx.fill(); //---------------------------------這個(gè)為要移動(dòng)的塊------------------------------------------------ var c_l = document.getElementById("puzzleLost"); //---------------------------------這個(gè)為要移動(dòng)的塊增加陰影------------------------------------------------ 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 () { //從原圖片,進(jìn)行設(shè)置處理再顯示出來(其實(shí)就是設(shè)置你想顯示圖片的位置2和3參數(shù),和框w高h(yuǎn)) 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(); //帶陰影,數(shù)字越高陰影越嚴(yán)重 ctx_l.shadowBlur = 10; //陰影的顏色 ctx_l.shadowColor = "black"; // ctx_l.fill(); 其實(shí)加這句就能有陰影效果了,不知道為什么加多個(gè)圖層 //分割畫布的塊 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(); //開始時(shí)間 var beginTime; //結(jié)束時(shí)間 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;">驗(yàn)證通過</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;">驗(yàn)證失敗:</span><span style="margin-left:4px;">拖動(dòng)滑塊將懸浮圖像正確拼合</span>'); $(".ver-tips").addClass("slider-tips"); setTimeout(function () { $(".ver-tips").removeClass("slider-tips"); }, 2000); self.error(); } } //0.5指動(dòng)畫執(zhí)行到結(jié)束一共經(jīng)歷的時(shí)間 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')); }) } } /** * 獲取該類型驗(yàn)證碼的一些參數(shù) */ 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; } /** * 設(shè)置驗(yàn)證成功的回調(diào)函數(shù) * @param success */ ImgIdentifying.prototype.setSuccess = function (successFunc) { this.successFunc = successFunc; } /** * 設(shè)置驗(yàn)證失敗的回調(diào)函數(shù) * @param success */ ImgIdentifying.prototype.setError = function (errorFunc) { this.errorFunc = errorFunc; }
其中init的方法,大家就可以抄啦,驗(yàn)證碼是這里生成的(感謝網(wǎng)上一些熱心網(wǎng)友提供的Mod,在此基礎(chǔ)上改的)。
2|0后端的設(shè)計(jì):
首先要有一個(gè)驗(yàn)證碼的接口,將一些常量和共同的方法抽象到接口中(接口最重要的作用就是行為的統(tǒng)一,意思是我如果知道這個(gè)是驗(yàn)證碼,那么必定就會(huì)有驗(yàn)證的方法,不管它是滑動(dòng)驗(yàn)證,圖形驗(yàn)證等,然后就可以放心的調(diào)用驗(yàn)證方法去獲取驗(yàn)證結(jié)果,下面過濾器設(shè)計(jì)就可以立馬看到這作用。具體java接口的說明會(huì)單獨(dú)寫篇文章),接口如下:
/** * 驗(yàn)證碼類的接口,所有驗(yàn)證碼必須繼承此接口 */ public interface I_Identifying<T> { String EXCEPTION_CODE = SystemStaticValue.IDENTIFYING_EXCEPTION_CODE; String IDENTIFYING = "Identifying"; //--------------以下為驗(yàn)證碼大體錯(cuò)誤類型,拋出錯(cuò)誤時(shí)候用,會(huì)傳至前端--------------- //驗(yàn)證成功 String SUCCESS = "Success"; //驗(yàn)證失敗 String FAILURE = "Failure"; //驗(yàn)證碼過期 String OVERDUE = "Overdue"; //-------以下為驗(yàn)證碼具體錯(cuò)誤類型,存放在checkResult------------- String PARAM_ERROR = "驗(yàn)證碼參數(shù)錯(cuò)誤"; String OVERDUE_ERROR = "驗(yàn)證碼過期"; String TYPE_ERROR = "驗(yàn)證碼業(yè)務(wù)類型錯(cuò)誤"; String ID_ERROR = "驗(yàn)證碼id異常"; String CHECK_ERROR = "驗(yàn)證碼驗(yàn)證異常"; /** * 獲取生成好的驗(yàn)證碼 * @param request * @return */ public T getInstance(HttpServletRequest request) throws Exception; /** * 進(jìn)行驗(yàn)證,沒拋異常說明驗(yàn)證無誤 * @return */ public void checkIdentifying(HttpServletRequest request) throws Exception; /** * 獲取驗(yàn)證結(jié)果,如果成功則為success,失敗則為失敗信息 * @return */ public String getCheckResult(); /** * 獲取驗(yàn)證碼的業(yè)務(wù)類型 * @return */ public String getIdentifyingType(); }
然后,設(shè)計(jì)一個(gè)具體的滑動(dòng)驗(yàn)證類去實(shí)現(xiàn)這個(gè)接口,這里只貼參數(shù):
/** * @author NiceBin * @description: 驗(yàn)證碼類,前端需要生成驗(yàn)證碼的信息 * @date 2019/7/12 16:04 */ public class ImgIdentifying implements I_Identifying<ImgIdentifying>,Serializable { //此次驗(yàn)證碼的id private String identifyingId; //此次驗(yàn)證碼的業(yè)務(wù)類型 private String identifyingType; //需要使用的圖片 private String imgSrc; //生成塊的x坐標(biāo) private int X; //生成塊的y坐標(biāo) private int Y; //允許的誤差 private int deviation = 2; //驗(yàn)證碼生成的時(shí)間 private Calendar calendar; //驗(yàn)證碼結(jié)果,如果有結(jié)果說明已經(jīng)被校驗(yàn),防止因?yàn)榫W(wǎng)絡(luò)延時(shí)的二次校驗(yàn) private String checkResult; //下面是邏輯代碼... }
上面每個(gè)變量都是一種校驗(yàn)手段,如calendar可以檢驗(yàn)驗(yàn)證碼是否過期,identifyingType檢驗(yàn)此驗(yàn)證碼是否是對(duì)應(yīng)的業(yè)務(wù)等。每多想一點(diǎn),別人破解就多費(fèi)勁一點(diǎn)。
后端驗(yàn)證碼的驗(yàn)證是不需要具體的類去調(diào)用的,而是被一個(gè)過濾器統(tǒng)一過濾,才過濾器注冊(cè)的時(shí)候,將需要進(jìn)行驗(yàn)證的路徑寫進(jìn)去即可,過濾器代碼如下:
r NiceBin * @description: 驗(yàn)證碼過濾器,幫忙驗(yàn)證有需要驗(yàn)證碼的請(qǐng)求,不幫忙生成驗(yàn)證碼 * @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 { //應(yīng)該攜帶驗(yàn)證碼信息的,結(jié)果沒有攜帶,那就是個(gè)非法請(qǐng)求 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 { } }
可以看到接口的用處了,之前在用戶申請(qǐng)驗(yàn)證碼時(shí),驗(yàn)證碼類是放到用戶session中的,所以這里直接取出調(diào)用checkIdentifying即可,不需要關(guān)系它到底是滑動(dòng)驗(yàn)證碼,還是圖片驗(yàn)證碼什么的。
總結(jié)
以上所述是小編給大家介紹的滑動(dòng)驗(yàn)證碼的設(shè)計(jì)與理解,希望對(duì)大家有所幫助,如果大家有任何疑問請(qǐng)給我留言,小編會(huì)及時(shí)回復(fù)大家的。在此也非常感謝大家對(duì)腳本之家網(wǎng)站的支持!
如果你覺得本文對(duì)你有幫助,歡迎轉(zhuǎn)載,煩請(qǐng)注明出處,謝謝!
相關(guān)文章
在C#和Java語(yǔ)言中for和foreach的區(qū)別詳解
這篇文章主要介紹了在C#和Java語(yǔ)言中for和foreach的區(qū)別詳解,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-09-09Loongnix安裝PyCharm Community 2020.2.3的教程詳解
這篇文章主要介紹了Loongnix安裝PyCharm Community 2020.2.3的教程詳解,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-11-11ibatis簡(jiǎn)單實(shí)現(xiàn)與配置
ibatis與hibernate一樣,同樣也是一種OR框架,OR框架有很多種,相對(duì)用的比較多的就是hibernate與ibatis,ibatis是一種白自動(dòng)化的ORM的實(shí)現(xiàn)2009-01-01BurpSuite超詳細(xì)安裝和基礎(chǔ)使用教程(已破解)
Burp?Suite?是用于攻擊web?應(yīng)用程序的集成平臺(tái)包含了許多Burp工具,它主要用來做安全性滲透測(cè)試,可以實(shí)現(xiàn)攔截請(qǐng)求、Burp?Spider爬蟲、漏洞掃描(付費(fèi))等類似Fiddler和Postman但比其更強(qiáng)大的功能,今天給大家介紹下BurpSuite安裝破解使用教程,感興趣的朋友一起看看吧2022-10-10