本文是驗證碼系列的第二篇铺韧。筆者的寫作順序基于心中排名,所以這次為大家?guī)淼氖菢O限驗證——GeeTest蟹略。
寫到這里可能會有人質(zhì)疑:難道阿里登失、騰訊、網(wǎng)易(以下簡稱ATN)的名氣和實力不是比極驗強嗎挖炬?不可否認揽浙,但我們討論的只是驗證碼:
邏輯上,知名度高意敛,綜合實力強馅巷,不代表垂直領(lǐng)域?qū)嵙σ惨欢◤?/strong>——當(dāng)然,并不否認ATN具備這樣的人才和實力草姻。這應(yīng)該很好理解钓猬。玩吉他的人都知道YAMAHA,但YAMAHA的吉他卻不見得是最好的——大師可能更喜歡Martin or Taylor撩独,或者純私人訂制敞曹。換句話說:如果你有一把YAMAHA FG730,發(fā)朋友圈會有很多人點贊豎大拇指综膀,但是也有人會給你一個手動微笑澳迫。
-
市場上,極驗在驗證碼領(lǐng)域已深耕多年剧劝,合作伙伴從直播纲刀、金融、電商担平,到資訊示绊、游戲、航旅暂论,甚至...政府面褐,且號稱有16w家企業(yè)正在使用。實力和人脈取胎,可見一斑展哭;而ATN的驗證碼服務(wù)只是其云平臺下的一個分支,官網(wǎng)均未專門介紹具體合作方和數(shù)量闻蛀。筆者雖沒有詳實的數(shù)據(jù)證明ATN接入方比極驗少匪傍,但從個人上網(wǎng)經(jīng)歷來看,極驗顯然更“面熟”觉痛。起碼簡書都在用了役衡。具體極驗官網(wǎng)可查詢,一張圖感受下:
優(yōu)勢往往就是這樣建立并擴大的:當(dāng)你和一個行業(yè)的Top 3合作愉快的時候
遺憾的是薪棒,極驗并沒有給筆者任何軟文費手蝎。鑒于此便不再繼續(xù)吹了。下面還是從產(chǎn)品和技術(shù)層面聊一聊俐芯。
產(chǎn)品背景
極驗?zāi)壳暗漠a(chǎn)品已經(jīng)升級為3.0:
其實這三代的產(chǎn)品棵介,目前是共存的勢態(tài):
存在即合理
1.0
1.0代表的是鍵盤輸入型字符驗證碼,開發(fā)成本低吧史,常用開源框架即可搞定(如Kaptcha 邮辽、JCaptcha)。其設(shè)計思路就一點:
如何讓生成的問題人可以解答贸营,而機器不可以
因此傳統(tǒng)驗證碼勢必要在讓機器感到壓力山大的事情上做文章吨述。但純圖片上做的文章,總是可以一物降一物:
當(dāng)網(wǎng)站本身被頻繁突破意義不大時锐极,自然不用考慮這些對抗上的“短兵相接”。但就此類驗證碼而言芳肌,最明顯的問題是:
- 每次都要敲鍵盤輸入
- 為了防范圖像識別破解灵再,便增大識別難度,導(dǎo)致有時候人都難以識別
更有甚者直接利用“人類(大學(xué)生或許更準(zhǔn)確)目前在認知學(xué)上對機器的優(yōu)勢“亿笤,在圖片上直接祭出了高數(shù)翎迁,將反人類做到極致:
當(dāng)然,上圖可能只是一個段子净薛⊥衾疲總之極驗官網(wǎng)已經(jīng)號稱對此沒有興趣:字符驗證碼不在服務(wù)范圍。而這也是大勢所趨:驗證碼本身沒有任何商業(yè)價值肃拜,為了阻擋機器會自帶反人類屬性痴腌。因此設(shè)計時一定要同時兼顧安全和用戶體驗雌团。對于一些電商類的網(wǎng)站,用戶體驗甚至是擺在首位的士聪。
2.0
2.0產(chǎn)品锦援,極驗稱其為”行為式驗證“。代表作是滑動拼圖驗證碼剥悟。目前也是各大驗證碼平臺的標(biāo)配:
為什么官網(wǎng)2.0的配圖不是點選驗證碼灵寺?點選同樣是標(biāo)配,極驗也有這款產(chǎn)品:
筆者認為区岗,主要原因是:
1)體驗不如滑動——點擊雖然比輸入更便捷略板,但識別文字依然要花大量時間,尤其是當(dāng)備選漢字遠多于待驗證漢字慈缔。
2)安全性不輸于滑動(如果設(shè)計足夠好)叮称,但其原理并非潮流所向——想要破解,首先要能摳出圖片中的漢字胀糜,保證所有的漢字都被識別颅拦,且能夠順序和待驗證漢字對上。這里窮舉法(隨便點三個)是沒有用的教藻,或者說成功概率很芯嗨А(1/P(n,m))。因為只要錯一個字就會刷新括堤÷到眨可以看出,其校驗原理更側(cè)重前端展示悄窃,并不過分依賴行為分析讥电。既讓用戶可見,又要在可見基礎(chǔ)上安全轧抗,就勢必帶來體驗的下降恩敌,這還是和字符驗證碼有殊途同歸的意思。
所以2.0強調(diào)行為横媚,也是基于思路的轉(zhuǎn)換:弱化前端展示纠炮,強化后臺分析,在不可見之處強化安全灯蝴。
對滑動拼圖而言恢口,滑到正確位置只是一個必要條件,用戶行為特征的提取才是核心穷躁。通過采集并分析用戶使用鼠標(biāo)拖動滑塊的行為特征(速度耕肩、頻率、耗時等)來判定是人還是機器。對用戶來說猿诸,體驗上比輸入或點選字符要更輕松愉快婚被,還兼具一定趣味性。但是便捷性往往和安全性相沖突:理論上只要機器模擬出人的行為两芳,就可以繞過驗證(有人可能會第一時間聯(lián)想到按鍵精靈)摔寨。其實是否輕松,就看對行為判定是否精準(zhǔn)了怖辆。這類判定,可以基于一些固定的規(guī)則(如滑動時間小于某個閾值)删顶,也可以引入機器學(xué)習(xí)分析特征量(如Kmeans做聚類分析)竖螃,看各家的思路和實力了。未來這些偏后端的技術(shù)才是賣點逗余,也不會輕易開源特咆。因此此類驗證碼更多還是各類驗證碼平臺提供,接入是有償?shù)摹?br> 知乎上有一位叫@darbra的兄弟录粱,對破解極驗2.0產(chǎn)品饒有興趣腻格。他總結(jié)出了selenium大法 和 requests基本法,號稱破率分別是98%和80%啥繁。其Github上的代碼7.4還有更新菜职。有興趣的讀者可以嘗試。
3.0
3.0時代則伴隨著人工智能的浪潮全面進化旗闽,強調(diào)的則是進一步弱化前端+強化機器學(xué)習(xí)酬核。前端越簡單越好,一個按鈕适室,一個滑塊嫡意,一個復(fù)選框即可,甚至...nothing捣辆。個人認為蔬螟,目前只有三家公司在這方面引領(lǐng)市場:Google,極驗和阿里汽畴。
1)Google第一篇已經(jīng)畫了大量篇幅介紹旧巾,已經(jīng)進化到“化無形為有形”的invisible reCAPTCHA,驗證碼實體完全透明整袁,是目前來說產(chǎn)品理念上最領(lǐng)先的(技術(shù)上就不談了)菠齿。
2)阿里和極驗是第二檔,區(qū)別在于一個是滑塊坐昙,一個是按鈕绳匀。相當(dāng)于Google的noCAPTCHA產(chǎn)品。
極驗的3.0產(chǎn)品思想,其實是官網(wǎng)提到的“驗證碼形如按鈕疾棵,更像按鈕一樣百搭”——也就是說這個按鈕會一直存在戈钢,只不過普通合法用戶,需要多點一下它來進行人機識別罷了是尔。只有非法用戶時才會出現(xiàn)滑動拼圖或圖文點選驗證殉了。
如果極驗有4.0,筆者大膽推測方向是invisible拟枚。
技術(shù)層面則強調(diào)使用了人工智能——其實各公司對應(yīng)的2.0產(chǎn)品薪铜,應(yīng)該都用到了。按照極驗官方的說法:
惡意程序模仿人類行為軌跡對驗證碼進行破解針對模擬恩溅,極驗擁有超過4000萬人機行為樣本的海量數(shù)據(jù)利用機器學(xué)習(xí)和神經(jīng)網(wǎng)絡(luò)構(gòu)建線上線下的多重靜態(tài)隔箍、動態(tài)防御模型識別模擬軌跡,界定人機邊界
極驗很可能是加強了人工智能的投入:例如新增更多特征量脚乡,優(yōu)化識別算法等蜒滩。畢竟機器學(xué)習(xí)還是一個數(shù)據(jù)為王的技術(shù),沒有足夠多的訓(xùn)練樣本是無法做到足夠精準(zhǔn)的奶稠。所以以極驗?zāi)壳暗氖袌龇蓊~俯艰,是有這個實力優(yōu)化的。但是加強了多少锌订,效果如何竹握,目前并沒有太好的印證方式,畢竟今年4月才上線瀑志∩辏看后期市場的反饋如何吧。
接入方式
同Google一樣劈猪,極驗的接入方式也如其UI一樣:清爽簡潔
按官網(wǎng)的說法:
- 引入初始化函數(shù)
<script src="gt.js"></script>
- 調(diào)用初始化函數(shù)進行初始化
initGeetest({
// 以下配置參數(shù)來自服務(wù)端 SDK
gt: data.gt,
challenge: data.challenge,
offline: !data.success,
new_captcha: data.new_captcha
}, function (captchaObj) {
// 這里可以調(diào)用驗證實例 captchaObj 的實例方法
})
結(jié)合配置參數(shù):
看似非常多昧甘,其實只有前面四個是必須,后面都是定制战得,按默認來均可不填充边。這樣的參數(shù)配置,作為一家專業(yè)驗證碼公司常侦,還是十分合理的——既要考慮到宕機問題浇冰,又得支持顧客的個性化需求。
參數(shù)含義聋亡,文檔已經(jīng)介紹的十分詳細肘习,Markdown排版閱讀起來也很舒適,筆者便不再贅言坡倔。值得一提的是漂佩,第二個參數(shù)
challenge
脖含,在前面介紹的Google驗證碼參數(shù)也出現(xiàn)過,且含義基本一樣投蝉⊙可能一半借鑒,一半致敬吧瘩缆。
源碼分析
細心的朋友會發(fā)現(xiàn)关拒,步驟1中加載的是一個本地路徑的gt.js
,而非從極驗服務(wù)器加載庸娱。按一般驗證碼公司的做法着绊,給出一個http://static.geetest.com/static/tools/gt.js 就夠了。極驗則考慮到了failover層面——如果自己服務(wù)器故障怎么辦涌韩?在目前機房容災(zāi)能力越來越強的今天畔柔,這么考慮仍然是一種非常專業(yè)和負責(zé)的做法。online offline皆可進行驗證臣樱,這恰恰也是極驗有別于其他驗證碼的特點之一。
下面還是回到gt.js
——最近喜歡看源碼腮考。雖然筆者前端經(jīng)驗并不豐富雇毫,但是覺得有趣的東西,還是忍不住分享:
gt.js
的主要功能是定義初始化驗證碼的函數(shù)initGeetest
踩蔚。
最開始的地方如下:
"v0.4.6 Geetest Inc.";
(function (window) {
"use strict";
if (typeof window === 'undefined') {
throw new Error('Geetest requires browser environment');
}
……
})(window);
"v0.4.6 Geetest Inc."
行首直接字符串秀出版本號和公司棚放,一目了然。
這種重點要說的是"use strict"
馅闽。
嚴以律己飘蚯,寬以待人
"use strict"
,嚴格模式福也,是Javascript非常好的一個特性局骤,可能會大大節(jié)約你查錯的時間。
先舉個例子:
var zombie = {
eyeLeft : 0,
eyeRight: 1,
// ... a lot of keys ...
eyeLeft : 1
}
mingo= 5;
-
eyeLeft
出現(xiàn)了兩次暴凑,你打算用哪個峦甩? -
mingo
會是一個全局變量,如果被用于各種嵌套循環(huán)中會怎樣现喳?
這段代碼嚴格模式下會拋出兩個錯誤凯傲,而非嚴格模式不會報。
它以嚴格換來了如下好處嗦篱,主要是以下兩點:
- 消除語法的一些不嚴謹之處冰单,減少怪異行為
- 消除代碼運行的一些不安全之處
最初網(wǎng)景在開發(fā)JavaScript可能存在一些考慮不周的情況,一些模棱兩可的特性被人詬病灸促,新手可能分不清是語法糖還是語法坑诫欠。
user stick
便是語言成熟化的一個方向涵卵。畢竟JavaScript將來會是一種全棧語言,尤其是在服務(wù)端Node.js逐漸強勢的今天呕诉。
好像扯遠了缘厢。其實筆者想說的是極驗代碼寫的非常嚴謹。有一個細節(jié):
var loadScript = function (url, cb) {
var script = document.createElement("script");
script.charset = "UTF-8";
script.async = true;
script.onerror = function () {
cb(true);
};
var loaded = false;
script.onload = script.onreadystatechange = function () {
if (!loaded &&
(!script.readyState ||
"loaded" === script.readyState ||
"complete" === script.readyState)) {
loaded = true;
setTimeout(function () {
cb(false);
}, 0);
}
};
script.src = url;
head.appendChild(script);
};
不知該寫法應(yīng)該是參考了JQuery如下源碼:
callback = errorCallback = xhr.onload =
xhr.onerror = xhr.onabort = xhr.onreadystatechange = null
這么寫主要還是因為:
IE的 script 元素支持onreadystatechange事件甩挫,不支持onload事件贴硫。FF則正好相反
如果要在一個<script src="x.js"> 加載完成執(zhí)行一個操作,F(xiàn)F使用onload事件就行了伊者,IE下則要結(jié)合onreadystatechange事件和this.readyState英遭。所以:
script.onload = script.onreadystatechange = function(){
if( ! this.readyState //這是FF的判斷語句,因為ff下沒有readyState亦渗,IE的readyState肯定有值
|| this.readyState=='loaded' || this.readyState=='complete' //這是IE的判斷語句
){
alert('loaded');
}
};
此外還有一個細節(jié):
gt.js
有一個盡量利用多CDN挖诸,使靜態(tài)文件盡可能加載成功的機制:
fallback_config: {
slide: {
static_servers: ["static.geetest.com", "dn-staticdown.qbox.me"],
type: 'slide',
slide: '/static/js/geetest.0.0.0.js'
},
fullpage: {
static_servers: ["static.geetest.com", "dn-staticdown.qbox.me"],
type: 'fullpage',
fullpage: '/static/js/fullpage.0.0.0.js'
}
}
其目的是加載滑動和點選的widget。在fullpage.js
(已混淆)開頭可見以下寫法:
!function (a, b) {
'use strict';
'object' == typeof module && 'object' == typeof module.exports ? module.exports = a.document ? b(a, !0) : function (a) {
if (!a.document) throw new Error('Geetest requires a window with a document');
return b(a)
}
: b(a)
}('undefined' != typeof window ? window : this, function (a, b) {
……
}
今天天氣不錯多律,挺風(fēng)和日麗的
這一段讓筆者想起中學(xué)時寫作的范文——JQuery就是這么玩的。JQuery向來以嚴謹和兼容性強大著稱搂蜓,究其根本是寫法的包容性狼荞。大家可以對照看一下其的源碼開頭,以最新版本為例:
/*!
* jQuery JavaScript Library v3.2.1
* https://jquery.com/
*
* Includes Sizzle.js
* https://sizzlejs.com/
*
* Copyright JS Foundation and other contributors
* Released under the MIT license
* https://jquery.org/license
*
* Date: 2017-03-20T18:59Z
*/
( function( global, factory ) {
"use strict";
if ( typeof module === "object" && typeof module.exports === "object" ) {
// For CommonJS and CommonJS-like environments where a proper `window`
// is present, execute the factory and get jQuery.
// For environments that do not have a `window` with a `document`
// (such as Node.js), expose a factory as module.exports.
// This accentuates the need for the creation of a real `window`.
// e.g. var jQuery = require("jquery")(window);
// See ticket #14549 for more info.
module.exports = global.document ?
factory( global, true ) :
function( w ) {
if ( !w.document ) {
throw new Error( "jQuery requires a window with a document" );
}
return factory( w );
};
} else {
factory( global );
}
// Pass this if window is not defined yet
} )( typeof window !== "undefined" ? window : this, function( window, noGlobal ) {
// Edge <= 12 - 13+, Firefox <=18 - 45+, IE 10 - 11, Safari 5.1 - 9+, iOS 6 - 9.1
// throw exceptions when non-strict code (e.g., ASP.NET 4.5) accesses strict mode
// arguments.callee.caller (trac-13335). But as of jQuery 3.0 (2016), strict mode should be common
// enough that all such attempts are guarded in a try block.
"use strict";
這里其實是一個閉包(Closures)“锱觯現(xiàn)今流行的一些 JS 庫中經(jīng)常見到以下形式的代碼相味。在閉包中,可以定義私有變量和函數(shù)殉挽,外部無法訪問它們丰涉,從而做到了私有成員的隱藏和隔離。而通過返回對象或函數(shù)斯碌,或是將某對象作為參數(shù)傳入一死,在函數(shù)體內(nèi)對該對象進行操作,就可以公開希望對外暴露的方法與數(shù)據(jù)输拇。
JQuery這一段的目的摘符,注釋已經(jīng)寫得非常清楚。其實就是為了保證不污染全局變量策吠,將所有的對象及方法創(chuàng)建都放到了factory函數(shù)中執(zhí)行逛裤。通過形參global來傳遞window變量,在利用factory創(chuàng)建jQuery對象以前猴抹,首先進行window變量的檢測带族。
module 和 module.exports主要是為了讓jQuery能夠以模塊的形式(如CMD)注入到?jīng)]有window.document變量的諸如Node.js的運行環(huán)境中,當(dāng)遇到這種情況蟀给,就不會在window中設(shè)置jQuery$變量蝙砌。要使用jQuery時阳堕,則是使用所返回的jQuery對象,如在Node.js中:
var jQuery = require("jquery")(window);
所以極限以"范文"開頭择克,也是考慮了同樣的事情恬总。
最后還是聊一聊UBC的部分(寫累了,突然想裝13)——筆者曾在阿里驗證碼里看到了一個類似的文件肚邢,所以對這個縮寫印象深刻——其實就是User Behavior Collection壹堰,給到后端做人機識別用。極驗的UBC在http://apiguard.geetest.com/dist/geeguard.js 這里骡湖。其實做的事情就是用戶行為收集贱纠。
老套路,先搜一波userAgent
——為什么响蕴?因為做這件事情必須要瀏覽器指紋谆焊,否則無法鎖定設(shè)備,也就缺少了一個關(guān)鍵風(fēng)控維度浦夷。
不出意料可以看到這樣一段代碼:
'_getKeys': function () {
return ['textLength',
'HTMLLength',
'documentMode'].concat(this._tagKeys).concat(['screenLeft',
'screenTop',
'screenAvailLeft',
'screenAvailTop',
'innerWidth',
'innerHeight',
'outerWidth',
'outerHeight',
'browserLanguage',
'browserLanguages',
'systemLanguage',
'devicePixelRatio',
'colorDepth',
'userAgent',
'cookieEnabled',
'netEnabled',
'screenWidth',
'screenHeight',
'screenAvailWidth',
'screenAvailHeight',
'localStorageEnabled',
'sessionStorageEnabled',
'indexedDBEnabled',
'CPUClass',
'platform',
'doNotTrack',
'timezone',
'canvas2DFP',
'canvas3DFP',
'plugins',
'maxTouchPoints',
'flashEnabled',
'javaEnabled',
'hardwareConcurrency',
'jsFonts',
'timestamp',
'performanceTiming'])
},
其實這段代碼就是基于多維度給瀏覽器采集一個指紋辖试。Github上有一款開源Js叫
fingerprintjs,目前已經(jīng)出了第二版劈狐,定位就是Modern & flexible browser fingerprinting library剃执,功能是一樣的,如下:
get: function(done){
var keys = [];
keys = this.userAgentKey(keys);
keys = this.languageKey(keys);
keys = this.colorDepthKey(keys);
keys = this.pixelRatioKey(keys);
keys = this.hardwareConcurrencyKey(keys);
keys = this.screenResolutionKey(keys);
keys = this.availableScreenResolutionKey(keys);
keys = this.timezoneOffsetKey(keys);
keys = this.sessionStorageKey(keys);
keys = this.localStorageKey(keys);
keys = this.indexedDbKey(keys);
keys = this.addBehaviorKey(keys);
keys = this.openDatabaseKey(keys);
keys = this.cpuClassKey(keys);
keys = this.platformKey(keys);
keys = this.doNotTrackKey(keys);
keys = this.pluginsKey(keys);
keys = this.canvasKey(keys);
keys = this.webglKey(keys);
keys = this.adBlockKey(keys);
keys = this.hasLiedLanguagesKey(keys);
keys = this.hasLiedResolutionKey(keys);
keys = this.hasLiedOsKey(keys);
keys = this.hasLiedBrowserKey(keys);
keys = this.touchSupportKey(keys);
keys = this.customEntropyFunction(keys);
var that = this;
this.fontsKey(keys, function(newKeys){
var values = [];
that.each(newKeys, function(pair) {
var value = pair.value;
if (typeof pair.value.join !== "undefined") {
value = pair.value.join(";");
}
values.push(value);
});
var murmur = that.x64hash128(values.join("~~~"), 31);
return done(murmur, newKeys);
});
}
有興趣的讀者可以試著了解懈息。
總結(jié)
個人觀點:極驗是國內(nèi)目前最好的驗證碼產(chǎn)品——不論是產(chǎn)品設(shè)計、文檔風(fēng)格摹恰、還是代碼專業(yè)性辫继。這么說是因為,其他平臺俗慈,鮮有這幾方面都不錯的姑宽。例如阿里最大的問題就是接入配置較為復(fù)雜(接入頁面插入多段代碼);網(wǎng)易則是點選驗證碼圖文結(jié)合過于丑陋闺阱,無視審美炮车;點觸文檔排版不佳...諸如此類。
另外一個好感的原因酣溃,可能是筆者坐標(biāo)武漢瘦穆,明白在武漢做互聯(lián)網(wǎng)之不易。不管是政府扶持力度赊豌,還是人才凝聚力扛或,都和一線城市有差距。
希望極驗越做越好碘饼。
后續(xù)不打算再介紹其他平臺的驗證碼熙兔。一來都大同小異悲伶,其他平臺亮點不多,反復(fù)論述意義不大住涉;二來自身疲乏麸锉,想轉(zhuǎn)移一下興趣點。下一篇可能會介紹機器學(xué)習(xí)在人機識別中的簡單應(yīng)用舆声。鄙人新手花沉,如有翻車,老司機多多包涵扶正纳寂。