百度空间 | 百度首页 
 
查看文章
 
登录系统中验证码的使用(防注册机SPAM)
2008-07-16 11:33
       登录系统中防止暴力破解账号 ,很有效的一个办法就是使用验证码(这里所说的都是图片的验证码),我一直以来也都是使用这种办法,现在来探讨一下关于验证码的实现方式以及一些弊端和解决办法。

        一个基本的验证码方案实现流程为:
        1、实现一个图片生成模块,然后用此模块实现一个输出图片的页面。这个页面每次每访问时都需要生成一个新的验证码,把验证码的值存入一个能跨请求(Request)的存储系统中(比如Session),然后输出这个验证码图片。

        2、在需要加验证码的页面引用1中的验证码图片地址(<img src='xxxxxxx' />)。在每次用户数据提交后如果页面不刷新,也需要重新加载验证码图片(即刷新验证码)。

        3、用户提交数据后,后台系统中拿到用户提交的验证码和存储系统中的验证码值作比较,如果不匹配则报错(这时候需页面需要生成新的验证码)。


        这样一个流程基本能实现一个简单的验证码方案,不过这种方案对用户体验却有个弊端:用户每次都得输入验证码,验证码越难认,输入越麻烦(我有过10几分钟没有完成yahoo注册的经验,其中一个问题就在于那个验证码太复杂,输错验证码后填写的注册信息全丢失)。如果你尝试登录Google,在N(10)次登录不成功之后,登录页就会出现一个验证码,之后的登录尝试都必须填入验证码。

       我现在分析怎样改进前面的方案,实现和Google登录系统一样的验证码效果(如果你的验证码是每次都需要的,则用不着下面的方法)。

       如果需要等到登录失败多次后再出验证码,那么需要在Session中存储一个登录次数,每次登录失败后都需要把次数追加。当失败次数达到临界值(N)时,登录页面就需要呈现验证码,这时候验证码的生成和提交就可以参照上面的步骤。在这种方案下,每次数据提交的时候都需要检查当前登录次数是否达到临界值,如果达到则验证用户提交上来的验证码,否则就不用验证。
     
       系统设计到这里,问题并没有完全解决,它还不能真正阻挡注册机。如果用Session存储验证码和失败次数,由于实现实际系统中的SessionID基本都是存在Cookie中,如果某个注册机访问你的页面后总是不发送Cookie(这时候SessionID也就不存在),这样Server端每次请求都产生新的Session,这样登录次数永远达不到临界值,也就不用验证码,绕过了访问验证。怎么解决这个问题?看下面分析。

       上面的那个安全漏洞产生的原因就是由于Session的实现机制导致:客户端请求时不发送SessionID,服务器就只能产生新的SessionID,“自动产生”是问题所在。当然这个问题并不是说Session机制的不好,只是在这里不适合罢了。我们需要自己模拟用户登录会话(Login Session),实现的方式如下:
        访问登录页面时,首先需要生成用户登录会话凭证(LoginToken,相当于SessionID,要随机,不能被枚举,要长点),然后把这个LoginToken输出到页面的Hidden字段中。同时用这个LoginToken作为key,验证码和登录次数作为value,把他们存储在类似memcached这样的外部存储系统中。当用户提交数据时同时提交这个LoginToken,服务器端根据这个LoginToken来获取相关的登录尝试次数和验证码进行比较,如果提交数据时丢失LoginToken或者LoginToken不存在则肯定为非法提交。


     这个问题是最近在面试中被问到的,当时回答有点乱,但大致思路不差,现在做一下总结。

   更新:下面的讨论对此方案有所补充。

类别:Web | 添加到搜藏 | 浏览() | 评论 (7)
 
最近读者:
 
网友评论:
1
2008-07-17 10:05 | 回复
实际上发送一个sessionId的cookie过去和你说的发LoginToken不也一样吗?如果用户每次访问都构造一个新的LoginToken发送过去,一样的不能判断是不是非法提交。 对于用户身份的验证,常规方法最终还是要依赖于cookie。因此如果客户端不提交sessionId这个cookie过去,同样可以认为是非法的验证请求。 LoginToken和sessionId的cookie字段,我觉得本质上没有什么区别,都是从服务器端产生的。只不过一个通过get方法,一个通过request header提交给服务器罢了。
 
2
2008-07-18 00:57 | 回复
实际上发送一个sessionId的cookie过去和你说的发LoginToken不也一样吗?如果用户每次访问都构造一个新的LoginToken发送过去,一样的不能判断是不是非法提交。 =>如果他发送自己的LoginToken过来,由于LoginToken的不重复性,所以服务器端无论如何不会验证成功的,只有使用你给他生成的那个才行,所以这样就会保证多次登陆使用同一个登陆会话。 对于用户身份的验证,常规方法最终还是要依赖于cookie。因此如果客户端不提交sessionId这个cookie过去,同样可以认为是非法的验证请求。 =>原理上客户端不发送sessionid的话,可以认为是非法请求。但是实际上所有的session实现的机制都是没有发现sessionid的情况下产生新的session,认为是新的会话的开始。除非你按照需求实现自己的session,那不就是文章中的方案了么? LoginToken和sessionId的cookie字段,我觉得本质上没有什么区别,都是从服务器端产生的。只不过一个通过get方法,一个通过request header提交给服务器罢了。 ==>LoginToken提交的方式不是关键,关键是在服务器端对它的处理。用LoginToken的验证方式能够准确跟踪一个登录用户。
 
3
2008-07-18 13:52 | 回复
访问登录页面时,首先需要生成用户登录会话凭证(LoginToken,相当于SessionID,要随机,不能被枚举,要长点),然后把这个LoginToken输出到页面的Hidden字段中。 ==>如何确保同个用户多次访问都不会获得新的会话凭证呢?如果用户能不断获得新的合法的LoginToken,那么他的登陆次数可以一直处在合法的范围内。
 
4
2008-07-18 22:34 | 回复
这确实是一个问题,http协议的这种无状态性,就导致服务器不能精准确定客户端。 不过我看可以从另外的方面入手,增加辅助的验证手段: 1、用ip来模糊识别客户端,追踪每个ip访问的次数和时间。 2、增加登陆频度的控制。如果某个ip访问太频繁,首先终止请求或者限制LoginToken的发放,综合使用访问频度和LoginToken一起来验证。例如某个ip访问登陆的次数太频繁后,严格限制这个ip的LoginToken。
 
5
2008-07-21 16:47 | 回复
hao ,收藏了
 
6
2008-07-27 11:52 | 回复
我的方法类似楼主的: 不管用session,还是cookies,还是页面字段,总得有个地方保存一个data,这个data叫他凭证也行。 data的生成是服务器当前时间的ticks+加登陆次数+当前IP组合加密的+随即数或GUID值。可以用普通的字符串链接而成。 然后有个key, 从data中md5在选取一段出来,或其他的算法。 用这个key+服务器端的IV,加密data. 好了。 提交上去的时候,根据key+iv解开data吧,可以解开的话,就判读时间和次数等。 ticks+随机数或GUID值,基本上可以保证唯一性,所以data每次都是不同的。所以登陆的时候应该有个时间的判断。比如登陆失败的次数及上次登陆时间。太短了。直接放弃。当然,没有提供凭证,直接放弃。没必要去考虑如果不提交凭证的情况。那根本就是没事找事干。
 
7
2008-07-27 12:09 | 回复
你的key和data是一起存放在客户端的吧。 好像怎么都是避免不了访问频度控制的
 
发表评论:
姓 名:
网址或邮箱: (选填)
内 容:
验证码: 请点击后输入四位验证码,字母不区分大小写
      

     

©2009 Baidu