深蓝居

关注MS的VS2008和SQL Server 2008

常用链接

统计

积分与排名

朋友

学习生活

最新评论

使用C#登录带验证码的网站

我在上一篇文章中已经讲解了一般网站的登录原来和C#的登录实现,很多人问到对于使用了验证码的网站该怎么办,这里我就讲讲验证码的原理和对应的登录方法。

验证码的由来

几年前,大部分网站、论坛之类的是没有验证码的,因为对于一般用户来说验证码只是增加了用户的操作,降低了用户的体验。但是后来各种灌水机器人、投票机器人、恶意注册机器人层出不穷,大大增加了网站的负担同时也给网站数据库带来了大量的垃圾数据。为了防止各种机器人程序的破坏,于是程序员想出了只有人眼能够识别的,程序不容易识别的验证码!

验证码是一个图片,将字母、数字甚至汉字作为图片的内容,这样一张图片中的内容用人眼很容易识别,而程序将无法识别。在进行数据库操作之前(比如登录验证、投票、发帖、回复、注册等等)程序首先验证客户端提交的验证码是否与图片中的内容相同,如果相同则进行数据库操作,不同则提示验证码错误,不进行数据库操作。这样各种机器人程序就被拒之门外了!

但是随着计算机科学的发展,模式识别等技术越来越成熟,于是编写机器人程序的家伙可以通过程序将直接写在图片中的内容识别出来,然后提交到服务器,这样验证码将形同虚设。为了防止机器人程序的识别,验证码的图片生成也不断在发展,加入干扰点、干扰线,文字变形、变换角度位置,颜色不同……各种防止计算机识别的技术也应用到验证码中。就在这两种技术的竞争中,于是便形成了我们现在看到的验证码,已经有很多人在抱怨“这是什么验证码哦,人眼都分辨不清楚是什么”,一切也是无奈。

验证码的使用

验证码是针对各种机器人程序的,所以验证码图片中的内容是不能存放在Cookie、HTML和URL中的,如果看到一个验证码图片的URL是http://xxxxxx.com/Expwd.aspx?code=1af8 而验证码图片中的内容就是1af8那将是十分可笑的事情。同时,如果通过抓包发现了Cookie中保存了验证码的值或者查看HTML时看到了形如:<input type="hidden" id="exPwd" name="exPwd" value="1af8"/>这样将验证码的内容放在隐藏元素中也是不可思议的。对于这些行为,显然是这个程序员不知道验证码是拿来干什么的,只是别人的网站上有验证码,与自己的网站也弄一个来赶时髦。另外还有一种好笑的是验证码看上去像是验证码,结果看HTML代码居然不是一个图片,而是一个<span>1</span><span>a</span><span>f</span><span>8</span>。大家不要不以为然,以上这几种情况还真是我现实生活中遇到过的,当年写投票机器人的时候遇到这种情况我最高兴了!!!

验证码的内容必须保存在服务器端,一般我们可以将随机生成的验证码的内容放入Session中,用户提交的时候将提交的内容与Session中的验证码进行比较判断。在生成验证码的页面后台代码可以写为:

protected void Page_Load(object sender, EventArgs e)
   {
       
string checkCode = CreateCode(4);
       Session[
"CheckCode"= checkCode;
       CreateImage(checkCode);
   } 

比如在登录进行验证的时候可以写为:

protected void btnLogin_Click(object sender, ImageClickEventArgs e)
  {
      
if (Session["CheckCode"== null)
      {
          UIHelper.Alert(Page, 
"验证码已过期,请重新输入");
          
return;
      }
      
if (Session["CheckCode"].ToString().ToLower() != txbCode.Text.ToLower())//验证码忽略大小写
      {
          UIHelper.Alert(Page, 
"验证码错误");
          
return;
      } 
//数据库验证…… 
}

 

使用C#登录带验证码的网站

前面我们已经对整个验证码的原理和使用有了基本的了解,现在言归正传,讲讲如何登录带验证码的网站。这里我们以CSDN的登录为例。

image

1.在IE中正常登录一次并把登录时候的数据包抓下来。

2.分析其中的登录原理如下:

1)请求http://passport.csdn.net/UserLogin.aspx页面,与服务器建立会话,服务器返回一个SessionID在HTTP的Header中,如下,其他内容我们可以忽略。

ASP.NET_SessionId=ydebagnqgiiixi2dvihfw355; path=/; HttpOnly,ABCDEF=; domain=csdn.net; expires=Tue, 22-Apr-2008 17:57:01 GMT; path=/,QWERTOP=; domain=csdn.net; expires=Tue, 22-Apr-2008 17:57:01 GMT; path=/,activeUserName=Guest; domain=csdn.net; expires=Tue, 22-Apr-2008 17:57:01 GMT; path=/,UserName=Guest; domain=csdn.net; expires=Tue, 22-Apr-2008 17:57:01 GMT; path=/,PName=; domain=csdn.net; expires=Tue, 22-Apr-2008 17:57:01 GMT; path=/,ClientKey=; expires=Tue, 22-Apr-2008 17:57:01 GMT; path=/,userid=0; expires=Tue, 22-Apr-2008 17:57:01 GMT; path=/,ClientKey=933ffb09-5096-4fbb-b90f-5f0bff335b41; path=/

2)该页面返回的HTML中有一个<input type="hidden" name="ClientKey" value="a50b14fa-2a75-4364-bbeb-3b498b72aa46" />这个值在登录提交时也需要,所以需要从HTML代码中分离出来。

3)将该SessionID作为Cookie的内容发送到验证码生成的页面http://passport.csdn.net/ShowExPwd.aspx 该页面将返回一个图片的二进制流。

4)将返回的二进制流转换为图片并呈现给用户。

Image img = new Bitmap(
          Http.GetStreamByBytes(
"http://passport.csdn.net" , "http://passport.csdn.net/ShowExPwd.aspx", b,
                                aspcookie, 
out header));//获得验证码图片
this.pictureBox1.Image = img;

 

5)用户输入用户名、密码和验证码,然后和同前面分离出的ClientKey按如下的格式POST到http://passport.csdn.net/UserLogin.aspx进行验证。

__EVENTTARGET=&__EVENTARGUMENT=&__VIEWSTATE=%2FwEPDwULLTE4NDgzMDI2NjcPFgIeCkZpbmlzaFN0YXloFgJmD2QWBAIBDxYCHgRUZXh0BQznlKjmiLfnmbvlvZVkAgIPZBYCAgMPZBYCAgEPFgIeB1Zpc2libGVoZBgBBR5fX0NvbnRyb2xzUmVxdWlyZVBvc3RCYWNrS2V5X18WAgUeY3RsMDAkQ1BIX0NvbnRlbnQkY2JfU2F2ZVN0YXRlBR1jdGwwMCRDUEhfQ29udGVudCRJbWFnZV9Mb2dpbr5SL%2FGtMqVCJ%2FCh4jH%2FXp4DhlVU&ctl00%24CPH_Content%24tb_LoginNameOrLoginEmail=studyzy&ctl00%24CPH_Content%24tb_Password=123&ctl00%24CPH_Content%24tb_ExPwd=wgssj&ClientKey=a50b14fa-2a75-4364-bbeb-3b498b72aa46&ctl00%24CPH_Content%24cb_SaveState=on&from=http%3A%2F%2Fhi.csdn.net%2Fmy.html&MailParameters=&MailParameters=&ctl00%24CPH_Content%24Image_Login.x=26&ctl00%24CPH_Content%24Image_Login.y=11

6)验证成功的话将返回包含用户信息(发帖数、积分、博客排名等等)的HTML,验证失败将返回具体的错误信息。

3.以上将CSDN的登录原理分析清楚了,那么接下来就是代码实现了,代码实现比较简单,我直接在上篇文章所使用的Demo代码上修改的,所以写的不是很漂亮,大家若有兴趣可以看看。/Files/studyzy/LoginCSDNDemo.rar
成功登录后如图:

image

现在当前用户已经成功登录了,那么接下来是要在CSDN上发表博客、论坛发帖只需要将当前的SessionID放入Cookie中,在提交时使用该Cookie即可。

【出自博客园深蓝居,转载请注明作者出处】
Tag标签: 登录,C#,验证码

posted on 2008-05-08 02:19 深蓝 阅读(3835) 评论(46)  编辑 收藏 所属分类: .Net开发

评论

#1楼  2008-05-08 03:14 works guo      

顶   回复  引用  查看    

#2楼  2008-05-08 07:28 Anders Liu      

越来越有趣了~   回复  引用  查看    

#3楼  2008-05-08 08:31 1-2-3      

学习了。   回复  引用  查看    

#4楼  2008-05-08 08:37 Lotto      

楼主用的snap shot有问题啊   回复  引用  查看    

#5楼  2008-05-08 08:55 SZW      

和楼主讨论几个问题:
1、如果用非对称加密后的验证码放在Cookie中,替换Session,以减小服务器负担,是否“值得”呢?
2、不管用Cookie还是Session,这两种方法我在使用过程中都发现有一个同样的问题(也是Cookie和Session的key相同导致的问题):当用户依次打开A和B两个网页后,回过头来在A网页输入验证码的时候,显然这时候验证是无法通过的(除非A、B验证码相同),Cookie或者Session存放的是B网页的验证码,虽然让用户以为自己输错了,再输一次其实没有什么损失,我只是想在技术上讨论一下有没有什么高效的方法可以解决这个问题呢?当然最好是不要简单的用Session["CheckCode1"] ,Session["CheckCode2"],那样会加强页面和程序的耦合程度,使用Session也会不合理地增加服务器负担。
3、我看到QQ空间里面,验证码是通过用户点击textbox后显示的(可能是用了ajax或者iframe),这种方法可以在一些情况下欺骗机器人,也是当前提高验证码强度比较提倡的方法,但相对常规的方法还是还有个缺点,那就是需要运行环的境支持,不知按照这种类似的思路,是否有进一步加强验证强度的方法?   回复  引用  查看    

#6楼  2008-05-08 08:58 邀月      

谢谢分享   回复  引用  查看    

#7楼  2008-05-08 09:15 zdleek      

学习了。   回复  引用  查看    

#8楼  2008-05-08 09:27 JKJ [未注册用户]

有意思   回复  引用    

#9楼  2008-05-08 09:28 狼Robot      

我还以为楼主把那个图片的验证码给搞定了呢。   回复  引用  查看    

#10楼  2008-05-08 09:33 Clark Zheng      

。。。还以为要识别验证码呢   回复  引用  查看    

#11楼  2008-05-08 09:45 4x4y.com [未注册用户]

验证码放在cookie中是可行的,前提是加密,放在session中,可能会一定程度上加重服务器负担,我看到QQ空间里面,验证码是通过用户点击textbox后显示的,这种方法一定程序上减轻了服务器负担,就是不是每个用户请求都生成验证码,而且用户需要登陆或者发表评论的时候,动态生成验证码,原理都一样,不过这种方法确实可以减少服务器生成验证码的时间   回复  引用    

#12楼  2008-05-08 09:54 hdl253      

太有意思了   回复  引用  查看    

#13楼  2008-05-08 10:01 anonmyous [未注册用户]

怎么识别验证码?   回复  引用    

#14楼  2008-05-08 10:10 Justin      

  回复  引用  查看    

#15楼 [楼主] 2008-05-08 10:14 深蓝      

@SZW
1.将验证码内容加密后放在Cookie中是可行的,但是这样验证的时候也要再做一次加密解密的操作才能比较验证码是否输入正确,内存是节约了,但是也一定程度上增加了CPU的负担,孰优孰劣需要权衡。
2.既然可以将验证码加密后放在Cookie中,那么我们也可以将验证码加密后放在隐藏域<input type="hidden" />中,也可以加密后放在ViewState中,这样可以解决您说的第二个问题。
3.我觉得QQ这种方式是为了减小服务器负担,不是加强了验证的强度。加强验证强度主要还是从验证码图形的生成上做文章,但是验证强度比较高了程序是无法识别出来,人也可能无法识别了。要做到程序无法识别人眼容易识别还是不容易啊。   回复  引用  查看    

#16楼  2008-05-08 10:23 nicye      

数据最好不要放在 cookie,大小有限,而且会增加请求包大小
假如你做过即时更新数据的页面就知道了(如股票)
  回复  引用  查看    

#17楼  2008-05-08 10:26 镜涛      

学习啦   回复  引用  查看    

#18楼  2008-05-08 10:33 scarroot      

只要把用户登陆后的cookie记录下来,然后在灌水发贴时把cookie附上,就可以模拟N个用户同时post贴,
看看偶写的csdn灌水机.

http://scarroot.cnblogs.com/   回复  引用  查看    

#19楼 [楼主] 2008-05-08 10:34 深蓝      

关于验证码的识别已经有很多程序做了,我倒是自己也做不出来,我认为验证码对程序来说是不可识别的(这也是验证码的目的),对于QQ的这种验证码要正确识别出来是相当有难度的,如果哪位大哥能够把QQ验证码正确识别出来了通知大家一声。对于直接在Image上DrawString的验证码识别倒是不难,园子里肯定有不少这方面的高手。   回复  引用  查看    

#20楼  2008-05-08 10:50 SZW      

@深蓝
的确正如你说的“要做到程序无法识别人眼容易识别还是不容易”,使用ajax还是使自动破解难度上升了一个台阶的(如果全部机器人完成的话至少需要自动获取焦点并且捕获数据流,而不是简单的破解静态图片),我记得有园友发过破解单个图片的程序,QQ空间那个验证码也在被破解的列表中,你可以找找看。   回复  引用  查看    

#21楼  2008-05-08 10:52 mafa      

要求不高的话,验证码可以加密放在cookie中,比博客园不加密存放好一点,但仍然是有问题的。人工识别出一个验证码后,只需要使用cookie锁定工具,然后就可以一直使用这个验证码,不用再识别了。

另外,有了楼主这样的工具,人工识别验证码再自动发帖也是很方便的,我想一分钟几十贴完全没有问题,所以在服务端加上时间限制才是对付灌水机器人比较好的解决方法。   回复  引用  查看    

#22楼  2008-05-08 10:54 xmlcss [未注册用户]

其实不一定要用验证码。
我觉得问题更能防止机器人。

比如让用户回答这样的问题,几天星期几,4乘5为多少(多少乘多少为随机),本站的域名是什么。

要加强的话还可以把问题以图片的方式显示。

问题尽量用一些所有人都知道的,同一个问题可以设置几种方法的描述。比如:现在是几月/说出现在的月份/现在是今年的第几个月。

程序识别文字相对简单,但要理解一句话的意思并回答这个就困难多了。

  回复  引用    

#23楼 [楼主] 2008-05-08 11:12 深蓝      

@SZW
我觉得您这句“如果全部机器人完成的话至少需要自动获取焦点并且捕获数据流”对使用c#来模拟用户登录有一点误解,ajax技术是提高用户体验的,不是用于提高验证码破解难度的,在程序看来就是向服务器发送的数据流,服务器返回的数据流,没有什么“自动获得焦点”这东西,程序操作的都是数据流,是从协议层来发包收包,不是按键精灵在IE上操作。
PS:xmlcss 提的使用问题来防止机器人确实更有效,但是国内现在这样的网站还没有看到几个。   回复  引用  查看    

#24楼  2008-05-08 11:38 airwolf2026      

学习了...哈哈.赶紧去试验一下.咦?找那个地方试验哩?嘎嘎...   回复  引用  查看    

#25楼  2008-05-08 13:59 badnewfish      

搞了半天还是要人去识别嘛!
我还以为能绕过图片了,唉···被骗了!   回复  引用  查看    

#26楼  2008-05-08 14:06 yesun [未注册用户]

还以为你能自动识别呢,那就牛B了   回复  引用    

#27楼  2008-05-08 14:34 SZW      

@深蓝
厄?我什么时候说用这个方法模拟用户登录了??我说的是用机器人破解验证码后自动登录阿!
使用Ajax的话,这个Ajax请求得到回应之前(也可以说textbox获得焦点或者这个方法被触发之前),第一次返回HTML流(包括图片二进制数据)里面不包含验证码图片数据,那么至少机器人不能在第一时间进行破解。这怎么就不能提高防破解的强度了呢?况且这种非同步载入的方法近来一直是反复被提到的高到防破解强度的一个手段,基本常识,你可以google一下。
PS:看了回复好像很多人和我一样都一开始被这个标题误导了……   回复  引用  查看    

#28楼  2008-05-08 14:44 SZW      

这里有一个静态的工具,算不上机器人,只是完成里面的一部分功能:
http://www.secdomain.com/captcha.html
这个好像是同步弄出来的,不是很明显,网上有很多类似界面的根据图片判断的,你也可以看下。   回复  引用  查看    

#29楼  2008-05-08 15:07 Zhuang miao      

楼主我太稀罕你了!!真格的!!最近正在整这方面的东西~你就天天更新给我提供思路!!感激涕零!!!留个MSN吧?   回复  引用  查看    

#30楼  2008-05-08 15:26 Zhuang miao      

我整的是校内网的模拟登录。。。可是总不成功~~楼主帮研究分析下吧~~哈哈   回复  引用  查看    

#31楼  2008-05-08 17:21 HappyQQ      

哈哈,谢谢楼主的精彩文章

最近也在玩这类东西,刚好你提供我一些思想,谢谢了!!!

  回复  引用  查看    

#32楼  2008-05-08 20:14 xiaoice [未注册用户]

大哥我用你的思路登录www.warlord.cn发现怎么也么得到正确的结果,就是登录不了,不知道什么原因,研究了好久了就是想不通,55555…………help me 3q!!!!   回复  引用    

#33楼  2008-05-08 20:22 jeff377      

不錯的分享,收藏。   回复  引用  查看    

#34楼  2008-05-08 20:53 niruo [未注册用户]

验证码的生成写在一个aspx.cs中(如WebImg.aspx),在另一个页面中显示验证码时,图片的路径指向WebImg.aspx.

但怎样实现"看不清,换一张"时,页面不刷新,验证码换了?

用异步回调是可以从新把图片的路径指向WebImg.aspx但是验证码不能重新输出来,显示的还是原来的验证码.

请求解决方法...   回复  引用    

#35楼 [楼主] 2008-05-08 22:11 深蓝      

@niruo
这个很简单啊,就是一个JS把图片的src换了而已,比如CSDN的是:
<a href="#" onclick="document.getElementById('MzImgExpPwd').src='ShowExPwd.aspx?temp='+ (new Date().getTime().toString(36)); return false">
重新获得验证码</a>   回复  引用  查看    

#36楼  2008-05-09 09:05 清风笑      

学习了   回复  引用  查看    

#37楼  2008-05-09 10:58 谈 [未注册用户]

2)该页面返回的HTML中有一个<input type="hidden" name="ClientKey" value="a50b14fa-2a75-4364-bbeb-3b498b72aa46" />这个值在登录提交时也需要,所以需要从HTML代码中分离出来。

LZ 这个我真没找到,能告诉我在那儿吗   回复  引用    

#38楼  2008-05-10 18:00 rex xiang      

--引用--------------------------------------------------
SZW: 和楼主讨论几个问题:
1、如果用非对称加密后的验证码放在Cookie中,替换Session,以减小服务器负担,是否“值得”呢?
2、不管用Cookie还是Session,这两种方法我在使用过程中都发现有一个同样的问题(也是Cookie和Session的key相同导致的问题):当用户依次打开A和B两个网页后,回过头来在A网页输入验证码的时候,显然这时候验证是无法通过的(除非A、B验证码相同),Cookie或者Session存放的是B网页的验证码,虽然让用户以为自己输错了,再输一次其实没有什么损失,我只是想在技术上讨论一下有没有什么高效的方法可以解决这个问题呢?当然最好是不要简单的用Session[&quot;CheckCode1&quot;] ,Session[&quot;CheckCode2&quot;],那样会加强页面和程序的耦合程度,使用Session也会不合理地增加服务器负担。
3、我看到QQ空间里面,验证码是通过用户点击textbox后显示的(可能是用了ajax或者iframe),这种方法可以在一些情况下欺骗机器人,也是当前提高验证码强度比较提倡的方法,但相对常规的方法还是还有个缺点,那就是需要运行环的境支持,不知按照这种类似的思路,是否有进一步加强验证强度的方法?
--------------------------------------------------------

关于问题1, 我很早前想过, 是否会存在这样一种状况? 我手动登陆, 记下此时的cookie, 然后利用此cookie在程序中登陆(如果加密中没有苛刻的条件限制的话, 例如有效期, 登陆来源等), 理论上可以通过验证吧?

关于问题2, 我没有找到一个比较有效的解决方式. 不过很明显, 此问题是和问题3关联的, 问题3中提到的需要时才生成验证码, 个人认为主要不是为了效率问题(虽然也会存在这样的因素), 而是为了减少问题2的问题出现, 每个页面, 只有需要的时候才生成验证码, 从而减少每打开一个页面就更新一下当前用户的验证码的情况, 也就是减少导致之前打开的页面验证码失效的情况.   回复  引用  查看    

#39楼  2008-05-13 14:37 数据绑定者      

mark   回复  引用  查看    

#40楼  2008-05-28 16:17 簡簡單單..      

Mark   回复  引用  查看    

#41楼  2008-06-01 23:21 他山居士 [未注册用户]

要摸拟discuz类网站比较困难,因为用户和密码都是加密,楼主是否进行一些探索?头都大了   回复  引用    

#42楼 [楼主] 2008-06-01 23:26 深蓝      

@他山居士
用户登录的方式都是一样的,就算是对用户名密码进行了加密那也肯定是用js来进行的加密,找出加密的算法,在代码中用同样的方式实现加密即可。然后Post加密的数据包。   回复  引用  查看    

#43楼  2008-06-02 13:45 想爱就去爱吧      

哈哈早就想弄个这东西今天总算是找到了啊谢谢lz   回复  引用  查看    

#44楼  2008-06-03 16:28 shenanigans [未注册用户]

借用楼主的代码,我对这个网站做了一些测试: http://s1.warlord.cn
发现主页取不到Set-Cookie,只有对 http://s1.warlord.cn/game/login.jsp 才能取到,而且只能取到一个,奇怪。

如果我就单独按照这个来处理,然后,在webResponse = (HttpWebResponse)httpWebRequest.GetResponse(); 提示:The remote server returned an error: (405) Method Not Allowed.

难道这个是此网站的独特性吗?楼主有空的话,能否帮我看看,我留了我的email,楼主可以看到的。   回复  引用    

#45楼  2008-06-05 19:07 这个文章不应该公布 [未注册用户]

这篇文章不该公布出来。 你没发现很多人是带着搞破坏的心情来的嘛?

文章是好文章,但是公布出来会造成更大的混乱。

就好像你打开了一个潘多拉盒子。

希望你认真想想这个问题。

最好不要公开。

我填的信箱是真实的, 有什么事可以和我讨论。 来信要注明你的身份

PS 这个验证码安全的问题我早已经发现了。现在正筹划一个有关验证码安全解决方案的项目。有兴趣可以联系我。   回复  引用    

#46楼  2008-06-09 11:29 qufox [未注册用户]

--引用--------------------------------------------------
这个文章不应该公布: 这篇文章不该公布出来。 你没发现很多人是带着搞破坏的心情来的嘛?

文章是好文章,但是公布出来会造成更大的混乱。

就好像你打开了一个潘多拉盒子。

希望你认真想想这个问题。

最好不要公开。

我填的信箱是真实的, 有什么事可以和我讨论。 来信要注明你的身份

PS 这个验证码安全的问题我早已经发现了。现在正筹划一个有关验证码安全解决方案的项目。有兴趣可以联系我。
--------------------------------------------------------



封闭是没用的,尽早我也会发现,我们讨论的是如何去避免,做只鸵鸟谁不会呀。   回复  引用    


我要啦免费统计