研究 大流量、高併發網站的驗證碼解決方案

最近不知道怎麼的,總是喜歡研究一些大型站點的一些功能的實現,這兩天看了下幾個大型站的驗證碼的實現,覺得有點意思。
於是在.Net下也實現了一套類似的機制。我們先來看看這幾個站的驗證碼功能的外在表現:
看QQ的,網站上有驗證的地方都可以看的到,我這裏提供個地址:http://pay.qq.com/login.shtml?url=http://pay.qq.com/
看看獲取驗證碼的地址是:http://ptlogin2.qq.com/getimage,而當前操作的域是:pay.qq.com,可見它的這個實現跟我們普通
的.Net下的實現是不一樣的。
大家看看這個網站就知道了:http://www.byf.com/member/member_login.aspx?url=http%3a%2f%2fwww.byf.com%2fmember%2findex.aspx
登錄頁面上的驗證碼跟當前的操作是在一個域下,至少可以肯定的是在同一個站點下。
驗證碼的地址是:http://www.byf.com/member/validate_img.aspx
這是外面可以看的出來的不一樣的地方。我們再來看看外表看不到的地方,藉助HttpWatch來看看QQ的:
GET /getimage HTTP/1.1
HTTP/1.1 200 OK
Server: tencent http server
Accept-Ranges: bytes
Pragma: No-cache
Content-Length: 2589
Set-Cookie: verifysession=4a8ea93680ebf2b7fbdab088121fb2c7fbb5f134443e2844fbb16da500e6c128773e6e9f25e25cf2; PATH=/; DOMAIN=qq.com;
Connection: close
Content-Type: image/jpeg
在請求驗證碼圖片的同時服務端往客戶端寫了cookie verifysession登錄提交的時候登錄服務器會獲取這個cookie。
再來看普通的驗證碼:
GET /member/validate_img.aspx HTTP/1.1
HTTP/1.1 200 OK
Date: Wed, 07 May 2008 02:26:07 GMT
Server: Microsoft-IIS/6.0
X-Powered-By: ASP.NET
X-AspNet-Version: 1.1.4322
Cache-Control: private
Content-Type: image/Gif; charset=gb2312
Content-Length: 1953
什麼都沒有,就是在服務端把請求得到的驗證碼數據存到了Session中,等提交的時候直接獲取驗證碼輸入框中的值和Session中的值比較。
再來看看taobao的:http://member1.taobao.com/member/register.jhtml?_lang=default
驗證碼地址:http://checkcode.taobao.com/auction/checkcode?sessionID=230bc9bc5e73ac5c7f49e6804a1e1d17
他是反過來的,沒有往客戶端寫cookie,而是驗證碼那邊獲取註冊這邊的session然後存到某個地方,提交的時候去那裏驗證。
我們還可以看到一個小hack在驗證碼的頭部有一段文字:
Copyright (c) 2006 by Yahoo! China Incorporated. All Rights Reserved.  有點搞笑哦,哈哈!!!
 
再來看看163的:http://reg.163.com/reg0.shtml
它的做法跟taobao一樣,沒有寫cookie,是驗證那邊獲取應用這邊的Session的。
再來看看baidu的 :
HTTP/1.1 200 OK
Date: Wed, 07 May 2008 02:47:05 GMT
Server: Apache
Set-Cookie: BDUSS=2pObG53NHlOZ1FlYm1iV29wZ1MtMHRDNmVFTkhXekNPN09OMWdGYUJTd3BwVWhJQlFBQUFBJCQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACkYIUgpGCFIS; path=/; domain=.baidu.com
Expires: Mon, 26 Jul 1997 00:00:00 GMT
Last-Modified: Wed, 07 May 2008 02:47:05 GMT
Cache-Control: no-store, no-cache, must-revalidate
Cache-Control: post-check=0, pre-check=0
Pragma: no-cache
Vary: Accept-Encoding
Content-Encoding: gzip
Content-Length: 2038
Connection: close
Content-Type: image/png
跟QQ的是一樣的模式,驗證那邊往應用這邊寫cookie。
還有xunlei 、google啊等。都是這樣的做法。。。肯定有它的道理在的!!!
 
綜上所述,這些大型站點都是把驗證碼服務器和應用服務器分開的。具體的做法有兩種:
1.獲取驗證碼的時候驗證碼服務器往客戶端寫驗證cookies,提交的時候服務端獲取這個cookie和提交上來的驗證碼,再去驗證碼服務器驗證。
2.獲取驗證碼的時候傳個應用這邊的session到驗證碼服務器那邊,提交的時候服務端把應用這邊的session和提交上來的驗證碼一起到驗證碼服務器驗證。
 
分析就到這裏了哦,既然清楚了原理,我們不妨來做個呵呵。
首先要解決的一個問題就是怎麼樣把客戶端請求的驗證碼數據存儲起來而且要兩邊都能夠訪問我是這樣解決的:
using System;
using System.Configuration;
using System.Collections;
using System.Collections.Generic;
using System.Text;
namespace Flyimg.Verify.Server
{
    public class CodeSession
    {
        /// <summary>
        /// 存放驗證數據鏈表
        /// </summary>
        private static LinkedList<CodeSession> VerifyCodeList = new LinkedList<CodeSession>();
        private string _SessionId;
        private string _VerifyCode;
        /// <summary>
        /// 構造函數
        /// </summary>
        /// <param name="strSessionId"></param>
        /// <param name="strVerifyCode"></param>
        public CodeSession(string strSessionId, string strVerifyCode)
        {
            _SessionId = strSessionId;
            _VerifyCode = strVerifyCode;
        }
        /// <summary>
        /// 添加驗證碼數據
        /// </summary>
        /// <param name="strSessionId"></param>
        /// <param name="strVerifyCode"></param>
        /// <returns></returns>
        public static string Add(string strSessionId, string strVerifyCode)
        {
            bool bResult = false;
            LinkedListNode<CodeSession> CurrentPlay = new LinkedListNode<CodeSession>(new CodeSession(strSessionId, strVerifyCode));
            try
            {
                //保持鏈表長度限制客戶端連接數
                if (VerifyCodeList.Count < int.Parse(ConfigurationManager.AppSettings["Capacity"].ToString()))
                {
                    VerifyCodeList.AddFirst(CurrentPlay);
                }
                else
                {
                    VerifyCodeList.RemoveLast();
                    VerifyCodeList.AddFirst(CurrentPlay);
                }
                bResult = true;
            }
            catch
            {
                bResult = false;
            }
            return bResult.ToString();
        }
        /// <summary>
        /// 刪除驗證碼數據
        /// </summary>
        /// <param name="strSessionId"></param>
        /// <param name="strVerifyCode"></param>
        public static void Remove(string strSessionId, string strVerifyCode)
        {
            CodeSession codesession = new CodeSession(strSessionId, strVerifyCode);
            VerifyCodeList.Remove(codesession);
        }
        /// <summary>
        /// 驗證驗證碼數據
        /// </summary>
        /// <param name="strSessionId"></param>
        /// <param name="strVerifyCode"></param>
        /// <returns></returns>
        public static string Verify(string strSessionId, string strVerifyCode)
        {
            int iResult = 0;
            foreach (CodeSession codesession in VerifyCodeList)
            {
                if (codesession._SessionId == strSessionId && codesession._VerifyCode == strVerifyCode)
                {
                    iResult = 1;
                    Remove(strSessionId, strVerifyCode);
                    break;
                }
            }
            return iResult.ToString();
        }
        /// <summary>
        /// 清除驗證數據
        /// </summary>
        public static void Clear()
        {
            VerifyCodeList.Clear();
        }
    }
}
有兩個主要的方法,ADD(添加)和Verify(驗證)還有個問題就是兩邊的應用能快速的訪問這個區域。我採用的是socket
沒有采用webservice的原因的這樣既可以分佈式的部署而且速度夠快。在驗證碼web端配置文件中配置好驗證碼Server的IP地址就可以了。
在驗證碼web端獲取到驗證碼數據後:
 
try
{
    //添加驗證到驗證服務器
    Common.AddToVerifyServer(Session.SessionID, this.strVerifyCode);
}
catch (Exception ex)
{
    Logger.Add(ex.Message);
}
//寫cookie
General.SetCookie("VerifyKey", Session.SessionID, "flyimg.cn");
這樣驗證碼web端的任務就完成了,驗證碼Server中就有數據顯示了:
 

提交的時候:
protected override void OnPostting(Object sender, DataEventArgs e)
{
    if (string.IsNullOrEmpty(Request.Form["UserAccounts"]))
    {
    strError1 = "請輸入用戶名!";
    }
    else if (string.IsNullOrEmpty(Request.Form["UserPwd"]))
    {
    strError2 = "請輸入密  碼!";
    }
    else if (string.IsNullOrEmpty(Request.Form["VerifyCode"]))
    {
    strError3 = "請輸入驗證瑪!";
    }
    else
    {
    bool bResult = false;
    try 
        {            
        //到驗證服務器驗證
        bResult = Common.Verify(ToolKit.Common.General.GetCookie("VerifyKey"), Request.Form["VerifyCode"]);
        }
        catch (Exception ex)
        {
        Logger.Add(ex);
        }
    if (bResult)
    {
        if (Request.Form["UserAccounts"] == "admin" && Request.Form["UserPwd"] == "123123")
        {
        General.SetCookie("user_id", "admin");
        string strReturnUrl = Request.QueryString["url"];
        if (!string.IsNullOrEmpty(strReturnUrl))
        {
            Response.Redirect(HttpUtility.UrlDecode(Request.QueryString["url"]));
        }
        else
        {
            Response.Redirect("/upload");
        }
        }
        else
        {
        strError2 = "用戶名或者密碼錯誤!";
        }
    }
    else
    {
        strError3 = "驗證瑪錯誤!";
    }
    }
}
這樣就完成了驗證:
 
好了。功能就是這樣實現的。這是用第一種方式實現的,稍微修改下就可以改成第二種方式了。
你可以嘗試一下哦
謝謝,歡迎大家交流!enjoy...

發佈了329 篇原創文章 · 獲贊 25 · 訪問量 198萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章