使用ASP.NET AJAX Control Toolkit中的NoBot控制項拒絕垃圾發布程式

來源:互聯網
上載者:User

本文來自我即將出版的《ASP.NET AJAX程式設計 第I卷 伺服器端ASP.NET AJAX Extensions與ASP.NET AJAX Control Toolkit(暫訂名)》第10章第1節。請各位朋友不吝給出建議和意見。

 

10.1 NoBot:拒絕機器人程式

NoBot 控制項可以為頁面中的表單提供類似CAPTCHA[注釋1]而卻無需任何使用者操作的驗證,以阻止機器人程式自動認可垃圾資訊。

 

10.1.1 應用情境

網路上的垃圾資訊似乎無處不在,從前是垃圾郵件、廣告等。而現在,這些無孔不入的垃圾資訊發行者又盯上了互連網上的各大網站。各種機器人程式(Bot)應運而生,它們可以自動在網路上爬行並尋找帶有評論或留言功能的頁面,隨即自動填寫表單並提交,其提交垃圾資訊的數量和品質更是讓傳統的手工發行者自愧不如。管理者往往一夜之間發現自己的網站下已經多了成千上萬條廣告,不但讓真正有用的資訊淹沒於其中,更是讓網站在效能上不堪重負。

由此,很多解決方案同樣應運而生,其中最著名的當屬各種驗證圖片了。這種驗證圖片中的文字由電腦隨機產生,並盡其所能地對其進行扭曲、變形、修飾、模糊,最終要達到的目的是只有聰明的人類才能夠分析出其中的內容(10-1所示),而當前水平的電腦卻只能夠望“圖”興歎。然後將使用者對這幅圖片的識別文本隨表單一起發送至伺服器。這樣,伺服器即可通過檢查用戶端輸入的識別文字的正確與否來判斷這是否是人類所為,也就達到了區分機器人程式和人類的目的。

圖10-1 極其複雜的驗證圖片

目前為止,這種做法非常有效,因為電腦影像處理能力的發展還不足以完全正確地識別出如此複雜的圖片中的文字。不過這樣做的缺點也很明顯——麻煩!每次提交一次表單都需要使用者仔細睜大雙眼分辨驗證圖象中的內容,並且輸入一串毫無意義、沒有任何連貫性的字元,真的是一種痛苦!而且,對於視力不便的殘障人士來說,這種驗證圖片更是將其拒之門外。

於是,某些網站提供了另外一種可選項,即對於視力不便的殘障人士,可以選擇聽一段朗讀,並輸入其中讀過的字母,通過聲音的方式來分辨人類和電腦,例如Hotmail的註冊頁面,10-2所示。

圖10-2 以識別聲音的方式來分辨人類和電腦

這樣似乎考慮得很周全了……不過,如果某個使用者不懂得英文,那豈不還是不能使用嗎?世界上有這麼多種語言,難道要提供每種語言的版本?而且,即使提供了所有語言的版本,對於麻煩這個致命的問題,也仍舊是無法解決啊!

有沒有一種無需使用者操作的,對使用者完全透明的驗證機器人程式的解決方案呢?ASP.NET AJAX Control Toolkit中提供的NoBot控制項即提供了這樣一種相對來說比較折衷的解決方案。

 

10.1.2 聲明文法以及常用屬性

NoBot控制項可以通過如下四種方式較為準確地判斷出進行當前操作的是否為人類:

  1. 讓用戶端瀏覽器執行一段JavaScript,並判斷其執行結果。機器人程式一般只是取得HTTP流的內容,對其分析並填寫其中表單之後即提交,這個過程中並不包含對瀏覽器功能的使用,也就更不會解析並運行頁面中的JavaScript得到正確的運行結果。且這段JavaScript既可以是一段簡單的純數學運算,例如123*4455=?,也可以是一些非常複雜的DOM操作,例如動態建立一個<div>,並返回它的位置等。這樣即強迫該程式只能夠在瀏覽器中使用,大多數機器人程式顯然對此等計算無力回天。
  2. 判斷用戶端是否儲存了本次工作階段狀態。一般來講,只有瀏覽器才會對工作階段狀態進行關注並儲存,而簡單的機器人程式則會完全忽略工作階段狀態資訊。
  3. 判斷用戶端從開始接受頁面到提交表單的時間間隔。機器人程式都比較講究“效率”,加上電腦的強大運算能力,幾乎可以在接收到頁面之後的瞬間就完成表單的填寫並提交回伺服器。而對於人類,顯然不可能在如此短暫的幾秒鐘時間之內就完成這樣複雜的一張表單。
  4. 判斷某段時間之內某個用戶端的提交次數。同樣,對於人類來說,沒有能力也沒有意義在比如一分鐘之內填寫同樣的表單100次,而對於機器人程式,則這很有可能是它們的一貫作風。

以上的四種方法,雖不能百分之百地完全阻止機器人程式,然而在大多數情況下還是非常有效且相當精確的。且使用這種方法最大的優勢就在於它省去了對使用者互動的需要,讓程式更加友好易用。

NoBot控制項在頁面中是完全不可見的,這似乎和我們潛意識中Ajax程式的那些眩目的介面效果沒什麼關係。不過從增強使用者體驗的角度來看,NoBot卻的確是一大進步,它也正符合了Ajax的最根本設計目標——提高使用者體驗。

聲明NoBot控制項的文法將類似如下所示:

<ajaxToolkit:NoBot
    ID="noBot"
    runat="server"
    ResponseMinimumDelaySeconds="2"
    CutoffWindowSeconds="60"
    CutoffMaximumInstances="5"
    OnGenerateChallengeAndResponse="noBot_GenerateChallengeAndResponse" />

NoBot控制項繼承於System.Web.UI.WebControls.CompositeControl,並間接繼承於System.Web.UI.WebControls.WebControl,也就擁有了這些控制項的所有屬性/方法/事件,聲明NoBot控制項時所常用的屬性標籤如表10-1所示。

表10-1 聲明NoBot控制項時的常用屬性標籤[注釋2]

  1. ResponseMinimumDelaySeconds: 一個合理的用戶端從開始接受頁面到提交表單的時間間隔,單位為秒。在該時間段之內的提交將被認為是機器人所為。
  2. CutoffWindowSeconds:指定一個統計同一用戶端提交次數的視窗時間段,單位為秒。在該時間段之內的提交次數超過CutoffMaximumInstances所指定的值將被認為是機器人所為。
  3. CutoffMaximumInstances:指定在視窗時間段內同一用戶端最多的提交次數。在CutoffWindowSeconds所指定的時間段之內的提交次數超過該值將被認為是機器人所為。
  4. OnGenerateChallengeAndResponse:指定GenerateChallengeAndResponse事件的處理函數。在該事件處理函數中我們可以設定強制瀏覽器執行的一段JavaScript以及其預期的執行結果。若瀏覽器的執行結果和預期結果不符,則本次提交將被認為是機器人所為。

 

10.1.3 樣本程式:阻止機器人程式的提交

NoBot控制項雖然設定起來比較簡單,但對其進行合理的配置卻並不容易,檢查標準太高或太低均難以達到我們的預期目標。接下來讓我們通過一個樣本程式來分析討論該控制項的使用方法。

首先在建立的頁面中添加ScriptManager控制項,然後添加一個TextBox和一個Button,用來類比出一個最簡單的輸入表單,再加入一個Label,用來顯示機器人程式檢測是否通過的資訊:

<asp:TextBox ID="tbSomething" runat="server"></asp:TextBox> 
<asp:Button ID="btnSubmit" runat="server" Text="Submit" /> 
<asp:Label ID="lbResult" runat="server"></asp:Label> 

接下來是NoBot控制項的聲明:

<ajaxToolkit:NoBot ID="noBot" CutoffWindowSeconds="10" CutoffMaximumInstances="2" 
  ResponseMinimumDelaySeconds="2" 
  OnGenerateChallengeAndResponse="noBot_GenerateChallengeAndResponse" 
  runat="server" />

我們先來看ResponseMinimumDelaySeconds屬性,在該樣本程式中,因為表單非常簡單,只有一個域,我們將其設定為較短的2(秒)。在實際開發中,我們應該根據表單的複雜程度估計使用者填寫需要的時間,並相應地配置該屬性。例如,對於如Hotmail中複雜的使用者註冊表單來講,將該屬性值設定為100秒都不足為過——在100秒之內能夠將該註冊頁面填寫完成的,除了機器人程式也只有天才了。

對於CutoffWindowSeconds屬性,這裡我們設定為10(秒)。該屬性值越大,則統計的時間段也就越長,判斷結果也就愈加有信服力,但同時這樣也會造成伺服器端更大的開銷。一般情況下,將該屬性設定為10-100是一個比較合理的選擇。

對於CutoffMaximumInstances屬性,這裡我們設定為2(次),配合CutoffWindowSeconds屬性,其含義即為在10秒鐘之內同一個用戶端最多可以提交2次,超過該次數的提交均被認為是機器人程式。在考慮設定該屬性時,我們也要考慮表單的複雜程度並相應地估計使用者填寫所需要的時間。

而對於OnGenerateChallengeAndResponse屬性,即GenerateChallengeAndResponse事件的處理函數,我們將在其中設定強制瀏覽器執行的JavaScript以及預期的結果,這裡將其指定為noBot_GenerateChallengeAndResponse(),函數的名稱無關緊要,讓開發人員能夠理解就好。該事件處理函數的簽名如下:

protected void noBot_GenerateChallengeAndResponse(object sender, NoBotEventArgs e) 

注意其中的類型為NoBotEventArgs的參數e,將在稍後用到。在該函數中,我們將動態產生一個隨機大小的<div>並添加到頁面的DOM樹中,且將該<div>的長寬乘積作為預期值儲存起來。同時將向頁面中寫入一段JavaScript,該JavaScript用來在用戶端運行時找到該<div>並取得其長寬的乘積。這樣在頁面回送之後,NoBot控制項就可以通過比較預期值與從用戶端得到的實際值是否相等來判斷用戶端是否為真正的瀏覽器,進而判斷用戶端是否為機器人。

在noBot_GenerateChallengeAndResponse()方法中,首先建立一個ASP.NET Panel,選擇Panel是因為該控制項將被呈現為HTML <div>元素,方便得到其長寬屬性:

Panel noBotPanel = new Panel(); 

接下來,產生兩個隨機數,將分別設定到該Panel的長和寬屬性上:

Random rand = new Random(); 
int width = rand.Next(80); 
int height = rand.Next(120); 

隨後,為這個Panel指定一個隨機的ID,指定ID是為了讓之後的JavaScript中可以在用戶端取到其產生的<div>,而選用隨機的ID是為了讓程式更加具有不確定性,進一步迷惑機器人程式:

noBotPanel.ID = string.Format("noBotPanel{0}", rand.Next(1000)); 

然後將上面產生的長、寬應用到該Panel上:

noBotPanel.Width = width; 
noBotPanel.Height = height; 

為了不干擾頁面的現有布局,我們還要設定一下該Panel的樣式,將其隱藏起來:

noBotPanel.Style.Add(HtmlTextWriterStyle.Visibility, "hidden"); 
noBotPanel.Style.Add(HtmlTextWriterStyle.Position, "absolute"); 

注意第一句實際上是設定了visibility: hidden;,而並沒有選擇我們常用的display: none;。因為若選用後者,則瀏覽器將認為其大小為0。

然後將該Panel添加為NoBot的子控制項,同樣是為了避免可能出現的對頁面結構的影響:

(sender as NoBot).Controls.Add(noBotPanel); 

然後設定將在瀏覽器中執行的這一段檢驗的JavaScript:

e.ChallengeScript = string.Format("var noBotPanel = document.getElementById('{0}'); noBotPanel.offsetWidth * noBotPanel.offsetHeight;", noBotPanel.ClientID); 

注意到這段JavaScript在運行時將首先通過該Panel的用戶端ID得到其實際<div>元素的引用,然後使用offsetWidth和offsetHeight得到其實際大小,並將其乘積返回。這段JavaScript賦值給了e.ChallengeScript,即NoBotEventArgs類型對象的ChallengeScript屬性上。

最後設定上面這段JavaScript的預期運行結果,非常簡單:

e.RequiredResponse = (width * height).ToString(); 

需要注意的是要將預期運行結果賦值給e.RequiredResponse,即NoBotEventArgs類型對象的RequiredResponse屬性。

這樣,若用戶端為真正的瀏覽器的話,則設定於e.ChallengeScript中的這段JavaScript將正常執行,並如我們所料地返回和e.RequiredResponse中完全一樣的預期結果。若是二者不匹配,則即可認為該用戶端為忽略了JavaScript的機器人程式。

完整的noBot_GenerateChallengeAndResponse()代碼如下:

protected void noBot_GenerateChallengeAndResponse(object sender, NoBotEventArgs e)
{
    Panel noBotPanel = new Panel();
 
    Random rand = new Random();
 
    int width = rand.Next(80);
    int height = rand.Next(120);
 
    noBotPanel.ID = string.Format("noBotPanel{0}", rand.Next(1000));
    noBotPanel.Width = width;
    noBotPanel.Height = height;
    noBotPanel.Style.Add(HtmlTextWriterStyle.Visibility, "hidden");
    noBotPanel.Style.Add(HtmlTextWriterStyle.Position, "absolute");
 
    (sender as NoBot).Controls.Add(noBotPanel);
 
    e.ChallengeScript = string.Format("var noBotPanel = document.getElementById('{0}'); noBotPanel.offsetWidth * noBotPanel.offsetHeight;", noBotPanel.ClientID);
 
    e.RequiredResponse = (width * height).ToString();
}

接下來同樣需要編寫的還有Page_Load()函數,其中我們將使用NoBot控制項進行驗證。因為只有在回送時才有驗證的必要,所以我們忽略掉頁面第一次載入的情況:

protected void Page_Load(object sender, EventArgs e)
{
    if (IsPostBack)
    {
        ……
    }
}

我們將在上述代碼中滿足IsPostBack的條件下編寫我們的驗證代碼。在其中建立一個NoBotState類型的枚舉,NoBot控制項的驗證結果就將存放於該枚舉中:

NoBotState state; 

NoBotState枚舉的可選值有如下幾種:

  1. Valid:表示驗證通過。
  2. InvalidBadResponse:表示前面自訂的JavaScript指令碼(即e.ChallengeScript)的運行結果和預期結果(即e.RequiredResponse)不符,驗證失敗。
  3. InvalidResponseTooSoon:表示用戶端完成表單的時間小於ResponseMinimumDelaySeconds所指定的時間,驗證失敗。
  4. InvalidAddressTooActive:表示用戶端在CutoffWindowSeconds指定的視窗時間內的請求超過了CutoffMaximumInstances所指定的數量,驗證失敗。
  5. InvalidBadSession:表示工作階段狀態驗證失敗,可能是用戶端並沒有儲存工作階段狀態,驗證失敗。
  6. InvalidUnknown:未知錯誤,驗證失敗。

然後將該NoBotState枚舉的引用傳遞到NoBot控制項的IsValid()方法中,該方法將返回一個布爾值,代表驗證是否成功。同時,傳遞進入的NoBotState也將被設定為相應的枚舉值。這樣,我們即可通過分辨IsValid()方法的傳回值判斷驗證是否成功,並作以相應操作:

if (noBot.IsValid(out state))
{
    ……
}
else
{
    ……
}

在驗證通過時,我們將給出一個樣本性的提示:

lbResult.Text = "您的資訊已經被提交!"; 

若驗證失敗,則將同樣給出詳細的錯誤提示:

string errorMessage = string.Empty;
switch (state)
{
    case NoBotState.InvalidAddressTooActive :
        errorMessage = "該IP地址在短時間內提交了過多的請求。";
        break;
    case NoBotState.InvalidBadResponse :
        errorMessage = "瀏覽器中檢測指令碼未被運行或運行結果不正確。";
        break;
    case NoBotState.InvalidBadSession :
        errorMessage = "ASP.NET工作階段狀態不可用。";
        break;
    case NoBotState.InvalidResponseTooSoon :
        errorMessage = "兩次回送時間間隔過短。";
        break;
    case NoBotState.InvalidUnknown :
        errorMessage = "未知錯誤。";
        break;
}
lbResult.Text = string.Format("請求被拒絕,原因:{0}", errorMessage);

出於示範的目的,上述代碼才如此耐心地對原因一一解釋。在實際的應用程式中,我們完全沒有必要如此的“友善”,簡單地提示“懷疑為機器人程式”即可,或是更加乾脆地用Response.End()結束本次HTTP會話,給機器人程式以顏色,也免得讓其瞭解我們程式中更多的NoBot實現細節。

作為參考,下面列出Page_Load()函數的完整代碼:

protected void Page_Load(object sender, EventArgs e)
{
    if (IsPostBack)
    {
        NoBotState state;
        if (noBot.IsValid(out state))
        {
            lbResult.Text = "您的資訊已經被提交!";
        }
        else
        {
            string errorMessage = string.Empty;
            switch (state)
            {
                case NoBotState.InvalidAddressTooActive :
                    errorMessage = "該IP地址在短時間內提交了過多的請求。";
                    break;
                case NoBotState.InvalidBadResponse :
                    errorMessage = "瀏覽器中檢測指令碼未被運行或運行結果不正確。";
                    break;
                case NoBotState.InvalidBadSession :
                    errorMessage = "ASP.NET工作階段狀態不可用。";
                    break;
                case NoBotState.InvalidResponseTooSoon :
                    errorMessage = "兩次回送時間間隔過短。";
                    break;
                case NoBotState.InvalidUnknown :
                    errorMessage = "未知錯誤。";
                    break;
            }
            lbResult.Text = string.Format("請求被拒絕,原因:{0}", errorMessage);
        }
    }
}

這樣即完成了本樣本程式,編譯並在瀏覽器中查看該頁面,將10-3所示。

圖10-3 初始化的表單

在文字框中輸入一些文字,確保等待了2秒鐘之後再提交頁面,將看到“您的資訊已經被提交!”驗證通過資訊,10-4所示。

圖10-4 驗證通過

迅速再點一下提交(2秒鐘之內),將看到10-5所示的“請求被拒絕,原因:兩次回送時間間隔過短。”驗證失敗資訊。

圖10-5 兩次回送時間間隔過短,驗證失敗

若是在10秒鐘之內提交次數超過兩次,將看到10-6所示的“請求被拒絕,原因:該IP地址在短時間內提交了過多的請求。”驗證失敗資訊。

圖10-6 同一IP地址在短時間內提交了過多的請求,驗證失敗

若是在瀏覽器中禁用了的JavaScript,則將看到10-7所示的“請求被拒絕,原因:瀏覽器中檢測指令碼未被運行或運行結果不正確。”驗證失敗資訊。

圖10-7 瀏覽器中檢測指令碼未被運行或運行結果不正確,驗證失敗

 

10.1.4 常見問題以及提示

NoBot可以完全代替傳統的驗證圖片嗎?

不可以。按照當前的電腦技術來看,驗證圖片將始終是最為精確的、無可替代的辨別機器人程式和真正使用者的最佳方法。對於NoBot控制項所採用的判斷規則,機器人程式均能夠通過某種方式進行類比並巧妙地繞開。且由於NoBot控制項需要統計過多的資訊,例如某個時間段內每個IP的提交次數、每個頁面的提交時間間隔等,也會在某種程度上影響伺服器端的執行效率。同時,若NoBot控制項配置不當,或是使用者使用某些不支援JavaScript的瀏覽器(例如行動裝置中的瀏覽器),則極易導致較高的誤判斷率乃至根本無法通過驗證,反而影響了使用者體驗。

而若是配置得當且伺服器端資源充沛,則NoBot控制項的優勢也非常明顯。所以,在選擇合適的驗證方法時,上述問題均要結合實際應用情境考慮周全,並做出恰當的決定。

如何選擇強制瀏覽器執行的JavaScript,即ChallengeScript?

由於這段JavaScript難以調試,所以應該儘可能的簡單。同時,為了避免機器人程式的成功預測,其中也要包含相當的不確定性。由此,前面樣本程式中示範的建立<div>並檢測其高度和寬度的乘積的方法非常適合:既足夠簡單,也有著相當的隨機性,足夠讓機器人程式難以捉摸。

[1]CAPTCHA即Completely Automated Public Turing Test to Tell Computers and Humans Apart(全自動的公開圖靈測試),其目的是讓電腦產生區分電腦和人類的程式演算法,這種程式必須能夠產生並評價出人類能很容易通過但電腦卻難以通過的測試。目前常見的驗證圖片等都屬於CAPTCHA。若想瞭解更多,請訪問“The CAPTCHA Project”網站:http://www.captcha.net/。

[2] ID屬性起到控制項標誌符的作用,我們都很熟悉,限於篇幅這裡不贅。下同。

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.