很早以前我就思考一个问题:验证码在理论上是否有存在的必要性呢?
人为操作与机器操作(非人为操作)的区别也许有点隐晦,我们可以设想一下,在一栋高科技大楼中你需要通过一道电子门,而这个门有一个识别程序,当判定通过者为人类才可以打开,如果是机器人那么就不开门。而你作为一个活生生的人类站在这道门前,却需要回答一个可笑的问题:"请问8+2=?" ,当你准备输入了10的时候,却按错了一个按键,门锁住了。这时候来了一个机器人,它同样准确无误地输入了10,这时候门打开了。这是不是一个看起来一个很愚蠢的电子门呢?我想大部分人在此都会自然而然的发出质疑:难道区分人类和机器就一定要用这种看似可笑的问题吗?为什么不从人类与机器的本质不同上加以区别呢,比如人类的生命体征、人体的热辐射等等...
其实现在的验证码就在做和这道电子门同样的事——在铁路订票的网站上煞费苦心弄出来一套奇形怪状的验证码,人眼都难以识别,机器是否不能识别还存疑。机器与人为操作的结果都是对服务器发送请求,而在发送网络请求的时候人为操作的痕迹已经完全没有了,换句话说,是无法验证请求操作是来自人还是机器。那么我们考虑网页上当一个人类用户去输入文字的时候是怎样的一个过程:光标从别处移入到可输入框内,光标变成一个"I形"的可输入符,点击鼠标左键,光标再次变成闪烁的输入提示符,重复(按下键盘按键,按键抬起),完成输入。这个过程是机器不会去做的,所有的自动登录的软件都是直接发送请求到服务器,机器并不去模拟用户的动作,因为没有任何意义(至少在本文发出去前是这样)。而这个人为动作我们却是我们验证操作来源的关键。我们在页面的脚本代码中加以验证鼠标是否在移入了指定的输入框时状态发生了改变?如果鼠标变成了输入符,则继续验证:是否在这之后点击量鼠标左键,如果有,则进入输入状态,继续验证:在输入状态下是否有按键按下和抬起?在按键按下和抬起中可以记录按下了哪一个键,然后在按键抬起的时候与真实输入框内的字符进行比较是否一致(好在现在的密码是没有中文字符的),如果一致则认为是一个人工的输入,之后在最后点击按钮的时候可以再次进行综合验证。思路就是这样,当然聪明的程序员可以把这个验证过程加以完善,我相信可以做到不需要验证码一样可以完全识别出有效的人为操作。我稍后会给出一个实例。
好了,举个例子说明一下。我们在网络上搜索"微信自动发消息",这是一个很合适的例子,借此可以论证能否不通过人为操作而实现自动发消息。看这个页面:
截至发文目前最新的微信网页版页面,我们注意一下下方的输入框(中间有字符"Jason"的一个框),通过谷歌浏览器查看它的ID是editArea, 然后再注意一下右下角的发送按钮:
查看得到它的class是 .btn .btn_send ,好的,之后我们对这两个控件进行一系列操作实验。
我们在网络上搜到微信自动发消息的关键代码:
document.getElementById(‘textInput‘).value=‘你想说的话‘;
document.getElementsByClassName(‘chatSend‘)[0].click();
原代码中的控件ID已经不适用,我们根据刚才得到的几个控件的ID改进一下并换成Jquery版本:
$(‘#editArea‘).append("Jason");
$(‘.btn_send‘).trigger("click");
上面的代码就是模拟在输入框内追加文字"Jason",然后模拟点击"发送"按钮,大家可以在谷歌浏览器或者firefox浏览器里的控制台输入这些代码,试验一下看看能不能自动发送消息?
好,经过我的试验,结果是没有发送出去。这是为什么呢?好困惑啊!难道是该页面用了什么特殊方法判断出了非人为操作吗?到这里我和大家一样困惑,不要急,我们继续探索。经过查看页面源码和脚本等信息,我发现这是由于该页面用了angular库对信息的处理上做了一些封装,我们以直接操作DOM元素的方式就无效了。我又去网上搜索了一下angular库的教程,针对该页面对之前的脚本进行了一些修改,修改后的代码如下:
var appElement = document.querySelector("#editArea");
var $scope = angular.element(appElement).scope();
$scope.$apply(function () {
$scope.editAreaCtn = "Jason";
}); $(‘.btn_send‘).trigger("click");
以上的代码在浏览器控制台执行后,页面自动发送了消息"Jason",至此我们成功实现了无人操作的自动发送消息。
我们通过一个反例,证明了我们的想法并没有实现,不过这只是开始。敬请期待下文...