滑动验证码的设计与理解
作者:半天想不出昵称的斌 发布时间:2022-09-18 08:34:28
在介绍之前,首先一个概念明确一个共识:没有攻不破的网站,只有值不值得。
这意思是说,我们可以尽可能的提高自己网站的安全,但并没有绝对的安全,当网站安全级别大于攻击者能得到的回报时,你的网站就是安全的。
所以百度搜到的很多验证码都已经结合了人工智能分析用户行为,很厉害。但这里只介绍我的小网站是怎么设计的。
大概逻辑:当需要验证码时,前端发送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_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即可,不需要关系它到底是滑动验证码,还是图片验证码什么的。
总结
以上所述是小编给大家介绍的滑动验证码的设计与理解网站的支持!
如果你觉得本文对你有帮助,欢迎转载,烦请注明出处,谢谢!
来源:https://www.cnblogs.com/top-housekeeper/p/11392439.html
猜你喜欢
- 本文分享的实例主要实现的是Python+matplotlib绘制一个有阴影和没有阴影的3D条形图,具体如下。首先看看演示效果:完整代码如下:
- 这个间歇性向上滚动js代码很适合做广告展示,友情链接等等。与平常的无缝向上连续滚动不同的是它每滚动一个就会停顿一会儿。<!DOCTYP
- 本文主要介绍了OpenCV全景图像拼接的实现示例,分享给大家,具体如下:left_01.jpgright_01.jpgStitcher.py
- 本文实例讲述了Python高级编程之消息队列(Queue)与进程池(Pool)。分享给大家供大家参考,具体如下:Queue消息队列1.创建i
- 前言最近由于在寻找方向上迷失自我,准备了解更多的计算机视觉任务重的模型。看到语义分割任务重Unet一个有意思的模型,我准备来复现一下它。一、
- 我将图形编辑程序分为两类:一类(是)绘图程序,利用这种程序可以一个像素一个像素(地)绘制图像;另外一类(是)制图程序,这种程序提供了一组对象
- 之前看到很多人一直都问这个问题,不过当时我没当一回事,因为在 CSS 中要垂直居中,多数是在有高度的情况下,或者容器高度不定的情况下才用,看
- 一、“无”的哲学佛家讲究“因果报应”,有果必有应。此段看似与主题没有血缘关系,实际讲的是“因”。我个人比较喜欢老子的道家思想,并喜欢以其思想
- 功能:获取android设备中某一个app的cpu和内存环境:python和adb使用方法:使用adb连接android设备,打开将要测试的
- 以下实例为通过用户输入两个数字,并计算两个数字之和:# -*- coding: UTF-8 -*-# Filename : test.py#
- 利用线程生成缩略图;读取当前路径下的png文件,在当前路径下生成6464,128128和32*32的缩略图。""&quo
- 何为自省在计算机编程领域里,自省是一种能力,是通过一定机制在程序运行时获知对象的类型及对象的内部结构,Python的自省能力还是很强大的,因
- 一. 如何调用def f1(arg1, arg2): print('f1', arg1, arg2)def f2
- [root@vm1 ~]# rpm -ivh groundwork-foundation-pro-1.6.1-67.noarch.rpm P
- 问题描述:已经用pip install jieba安装好jieba分词工具,但是在Jupyter 里import jieba运行一直提示Im
- “Be conservative in what you send; be liberal in what you accept. &nbs
- 前言小程序跳一跳最近很火,之前爆出微信游戏小程序漏洞,网上也不乏大神。这里就用一大神的python脚本来刷下高分。 跳一跳python脚本传
- 目录项目地址运行环境运行方法数据爬取(jd.comment.py)模型训练(train.py)情感分析(sentiment.analysis
- 描述super() 函数用于调用下一个父类(超类)并返回该父类实例的方法。super 是用来解决多重继承问题的,直接用类名调用父类方法在使用
- 前言写程序已经丢掉很长一段时间了,最近觉得完全把技术丢掉可能是个死路,还是应该捡起来,所以打算借CSDN来记录学习过程, 由于以前没事的时候