最近系统经常遇到广告机发的垃圾信息攻击。很多人想到的方法是增强验证码的难度,或者做一些问答来防止。但是这样在防止广告机的同时,对正常用户的使用和体验也造成很大困难。这里提供一个智能验证码的思路。即:在短时间内成功使用验证码后,验证码使用频率记录+1。短时间内下次生成时,将自动增加长度和难度。终极变态验证码就是扭曲的中文即可。大致代码如下:
建立一个c_iphistory数据表,用来记录验证码使用频率。
CREATE TABLE IF NOT EXISTS `c_iphistory` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `ip` varchar(255) NOT NULL,
  `num` int(11) NOT NULL,
  `lastdateline` int(11) NOT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `ip` (`ip`)
) ENGINE=MyISAM  DEFAULT CHARSET=utf8 COMMENT='使用验证码的ip对应次数';
生成验证码部分:$freq = 60;//定义洪水攻击的频率
//检查是否已存储
$tmp = db::r("select * from c_iphistory where ip='$_G[ip]'");
$len = 2;
$type = 0;
if($tmp && $tmp['num'] > 0) {
$num = $tmp['num'];
$timeout = TS - $tmp['lastdateline'];
$num -= floor($timeout / $freq);//洪水指数降多少级
if($num < 0) $num = 0;
$len += $num;
} if($len > 3){//倍增难度,长度2-5位
$type = floor($len / 4);
if($type > 8) {
$type = 8;
$len -= 34;
}else{
$len   = $len % 4;
$len += 2;
}
}else{
$len += 2;
}
$secode = s::rrand($len, $type);
//下面就用$secode生成图片并记录到验证码库中以备验证即可。//************************函数部分****************************//
/**
 * 随机数 @zairwolf
 *
 */
function rrand($len, $type = 7) {//1 - Number //2 - Lower Char //4 - Upper Char //8 - Chinese
mt_srand((double)microtime() * 1000000);
switch($type) {
case 0:
$charlist = '012';
break;
case 1:
$charlist = '0123456789';
break;
case 2:
$charlist = 'abcdefghijklmnopqrstuvwxyz';
break;
case 3:
$charlist = '0123456789abcdefghijklmnopqrstuvwxyz';
break;
case 4:
$charlist = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
break;
case 5:
$charlist = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ';
break;
case 6:
$charlist = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
break;
case 7:
$charlist = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
break;
case 8://使用中文
global $Ccharlist;
if(!$Ccharlist) $Ccharlist = file_get_contents("lib/disturbTxt.lib");
break;
}
$str = '';
if($type == 8) {
$max = strlen($Ccharlist) / 3-1;
for($i = 0; $i < $len; $i++) $str .= substr($Ccharlist, mt_rand(0, $max) * 3, 3);
}else {
$max = strlen($charlist) - 1;
for($i = 0; $i < $len; $i++) $str .= $charlist[mt_rand(0, $max)];
}
return $str;
}
验证码验证部分//在验证码检查确认正确之后,进行如下操作
$freq = 60;//定义洪水攻击的频率
//检查是否已存储
$tmp = db::r("select * from c_iphistory where ip='$ip'");
if(!$tmp) {
$s = array(
'ip' => $ip,
'num' => 1,
'lastdateline' => TS,
);
db::i("insert into c_iphistory set ".sqlcol($s));
}else{//更新洪水攻击记录
$timeout = TS - $tmp['lastdateline'];
if($timeout > $freq){//超过1分之前发的,已有记录--
$num = $tmp['num'] - 1;
if($num < 0) $num = 0;
$s = array(
'num' => $num,
'lastdateline' => TS,
);
db::q("update c_iphistory set ".sqlcol($s)." where id='$tmp[id]'");
}else{
$num = $tmp['num'] + 1;
$s = array(
'num' => $num,
'lastdateline' => TS,
);
db::q("update c_iphistory set ".sqlcol($s)." where id='$tmp[id]'");
}
}思路供参考。TS为time()。其它几个地方,估计大家都能看懂。

解决方案 »

  1.   

    本帖最后由 PhpNewnew 于 2013-02-06 14:31:09 编辑
      

  2.   

    刚接触PHP,代码看不太懂。我的疑问是:这样使用数据库,对服务器会造成多大的压力?
      

  3.   

    想法不错,而且现在百度、新浪微博都是这么做的。不过实际中可不能把这个值记录在mysql中,否则光这个查库和写库操作就够受的了。
    可以在内存数据库中使用hash表来存储这个值,hash key可以用session_id。