研究 大流量、高并发网站的验证码解决方案

最近不知道怎么的,总是喜欢研究一些大型站点的一些功能的实现,这两天看了下几个大型站的验证码的实现,觉得有点意思。
于是在.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...

posted @ 2013-06-04 14:17  夏至冬末  阅读(307)  评论(0编辑  收藏  举报