目錄
- 1|0js類的設(shè)計:
- 2|0后端的設(shè)計:
在介紹之前,首先一個概念明確一個共識:沒有攻不破的網(wǎng)站,只有值不值得。
這意思是說,我們可以盡可能的提高自己網(wǎng)站的安全,但并沒有絕對的安全,當(dāng)網(wǎng)站安全級別大于攻擊者能得到的回報時,你的網(wǎng)站就是安全的。
所以百度搜到的很多驗證碼都已經(jīng)結(jié)合了人工智能分析用戶行為,很厲害。但這里只介紹我的小網(wǎng)站是怎么設(shè)計的。
大概邏輯:當(dāng)需要驗證碼時,前端發(fā)送ajax向后臺請求相關(guān)數(shù)據(jù)發(fā)送回前端,由前端生成(與后端生成圖片,然后傳送圖片到前端的做法相比安全性要差很多。但也是可以預(yù)防的,后端可以對此Session進行請求記錄,如果在一定時間內(nèi)惡意多次請求,可以進行封禁ip等對策),驗證完成后,后臺再對傳回的數(shù)據(jù)進行校驗。
效果圖:
1|0js類的設(shè)計:
1.定義一個驗證碼父類,因為目前只有這一個驗證類型,倘若以后再要擴展其他驗證類型呢。那么它們之間肯定有很多公共之處(如:驗證成功、失敗的回調(diào),獲取驗證碼的類型,獲取驗證結(jié)果等),所以這些共同點可以提煉出來,下面是我目前的父類樣子:
/**
* 驗證碼的父類,所有驗證碼都要繼承這個類
* @param id 驗證碼的唯一標識
* @param type 驗證碼的類型
* @param contentDiv 包含著驗證碼的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é)點內(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ù)
* 驗證成功后進行調(diào)用
* this需要指具體驗證類
* @param result 對象,有對應(yīng)驗證類的傳遞的參數(shù),具體要看驗證類
*/
Identifying.prototype.success = function (result) {
if(this.successFunc instanceof Function){
this.successFunc(result);
}
}
/**
* 驗證失敗發(fā)生錯誤調(diào)用的函數(shù)
* @param result
*/
Identifying.prototype.error = function (result) {
if(this.errorFunc instanceof Function){
this.errorFunc(result);
}else{
//統(tǒng)一處理錯誤
}
}
/**
* 獲取驗證碼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傳遞的參數(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;
//這個要轉(zhuǎn)移到后臺
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;">按住左邊滑塊,拖動完成上方拼圖/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é)點的canvas畫布元素
//---------------------------------這一塊是圖片中央缺失的那一塊--------------------------------------
var ctx = c.getContext("2d");
ctx.globalCompositeOperation = "xor";
//設(shè)置陰影模糊級別
ctx.shadowBlur = 10;
//設(shè)置陰影的顏色
ctx.shadowColor = "#fff";
//設(shè)置陰影距離的水平距離
ctx.shadowOffsetX = 3;
//設(shè)置陰影距離的垂直距離
ctx.shadowOffsetY = 3;
//rgba第四個參數(shù)是透明度,前三個是三原色,跟rgb比就是多了第四個參數(shù)
ctx.fillStyle = "rgba(0,0,0,0.8)";
//beginPath() 方法開始一條路徑,或重置當(dā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);
//畫筆才開始移動到指定坐標,之間畫一條直線
ctx.lineTo(X + d, Y);
//繪制一條貝塞爾曲線,一共四個點確定,開始點(沒在參數(shù)里),和兩個控制點(1和2參數(shù)結(jié)合,3和4參數(shù)結(jié)合),結(jié)束點(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()成對出現(xiàn)
ctx.closePath();
//進行繪制
ctx.stroke();
//根據(jù)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 () {
//從原圖片,進行設(shè)置處理再顯示出來(其實就是設(shè)置你想顯示圖片的位置2和3參數(shù),和框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();
//帶陰影,數(shù)字越高陰影越嚴重
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;
//結(jié)束時間
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指動畫執(zhí)行到結(jié)束一共經(jīng)歷的時間
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'));
})
}
}
/**
* 獲取該類型驗證碼的一些參數(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è)置驗證成功的回調(diào)函數(shù)
* @param success
*/
ImgIdentifying.prototype.setSuccess = function (successFunc) {
this.successFunc = successFunc;
}
/**
* 設(shè)置驗證失敗的回調(diào)函數(shù)
* @param success
*/
ImgIdentifying.prototype.setError = function (errorFunc) {
this.errorFunc = errorFunc;
}
其中init的方法,大家就可以抄啦,驗證碼是這里生成的(感謝網(wǎng)上一些熱心網(wǎng)友提供的Mod,在此基礎(chǔ)上改的)。
2|0后端的設(shè)計:
首先要有一個驗證碼的接口,將一些常量和共同的方法抽象到接口中(接口最重要的作用就是行為的統(tǒng)一,意思是我如果知道這個是驗證碼,那么必定就會有驗證的方法,不管它是滑動驗證,圖形驗證等,然后就可以放心的調(diào)用驗證方法去獲取驗證結(jié)果,下面過濾器設(shè)計就可以立馬看到這作用。具體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 = "驗證碼參數(shù)錯誤";
String OVERDUE_ERROR = "驗證碼過期";
String TYPE_ERROR = "驗證碼業(yè)務(wù)類型錯誤";
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;
/**
* 獲取驗證結(jié)果,如果成功則為success,失敗則為失敗信息
* @return
*/
public String getCheckResult();
/**
* 獲取驗證碼的業(yè)務(wù)類型
* @return
*/
public String getIdentifyingType();
}
然后,設(shè)計一個具體的滑動驗證類去實現(xiàn)這個接口,這里只貼參數(shù):
/**
* @author NiceBin
* @description: 驗證碼類,前端需要生成驗證碼的信息
* @date 2019/7/12 16:04
*/
public class ImgIdentifying implements I_IdentifyingImgIdentifying>,Serializable {
//此次驗證碼的id
private String identifyingId;
//此次驗證碼的業(yè)務(wù)類型
private String identifyingType;
//需要使用的圖片
private String imgSrc;
//生成塊的x坐標
private int X;
//生成塊的y坐標
private int Y;
//允許的誤差
private int deviation = 2;
//驗證碼生成的時間
private Calendar calendar;
//驗證碼結(jié)果,如果有結(jié)果說明已經(jīng)被校驗,防止因為網(wǎng)絡(luò)延時的二次校驗
private String checkResult;
//下面是邏輯代碼...
}
上面每個變量都是一種校驗手段,如calendar可以檢驗驗證碼是否過期,identifyingType檢驗此驗證碼是否是對應(yīng)的業(yè)務(wù)等。每多想一點,別人破解就多費勁一點。
后端驗證碼的驗證是不需要具體的類去調(diào)用的,而是被一個過濾器統(tǒng)一過濾,才過濾器注冊的時候,將需要進行驗證的路徑寫進去即可,過濾器代碼如下:
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 {
//應(yīng)該攜帶驗證碼信息的,結(jié)果沒有攜帶,那就是個非法請求
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中的,所以這里直接取出調(diào)用checkIdentifying即可,不需要關(guān)系它到底是滑動驗證碼,還是圖片驗證碼什么的。
總結(jié)
以上所述是小編給大家介紹的滑動驗證碼的設(shè)計與理解,希望對大家有所幫助,如果大家有任何疑問請給我留言,小編會及時回復(fù)大家的。在此也非常感謝大家對腳本之家網(wǎng)站的支持!
如果你覺得本文對你有幫助,歡迎轉(zhuǎn)載,煩請注明出處,謝謝!
您可能感興趣的文章:- js+canvas實現(xiàn)滑動拼圖驗證碼功能
- 使用puppeteer破解極驗的滑動驗證碼
- Java實現(xiàn)滑動驗證碼的示例代碼
- selenium+java破解極驗滑動驗證碼的示例代碼
- 使用 Node.js 模擬滑動拼圖驗證碼操作的示例代碼
- js插件實現(xiàn)圖片滑動驗證碼
- Java selenium處理極驗滑動驗證碼示例