如何限制登录尝试次数 - PHP&MySQL&CodeIgniter

7
我希望能够基于失败的尝试次数来限制登录尝试,但我有一些问题。
我应该使用MySQL吗?(听说这可能会对数据库造成压力) 我应该为每个用户和整个系统进行限制,还是只针对整个系统?(以防止普通人猜测密码) 我该如何计算我的阈值?(使其自动适应变化/增长) 我应该如何检索此阈值?每次失败时查询/计算还是存储在缓存中? 我应该使用什么来进行限制?(看到有人回答说sleep()可能会对服务器造成压力)
有人有示例代码吗?
我对此很新,所以感谢您的帮助! 谢谢
3个回答

6

我在phunction中使用APC实现了简易限流机制,以下是我的使用方法:

// allow 60 requests every 30 seconds
// each request counts as 1 (expensive operations can use higher values)
// keep track of IPs by REMOTE_ADDR (ignore others)

$throttle = ph()->Throttle($ttl = 30, $exit = 60, $count = 1, $proxy = false);

if ($throttle === true)
{
    // IP exceded 30 requests in the last 60 seconds, die() here
}

else
{
    // $throttle is a float
    // number of requests in the last 30 seconds / 30 seconds

    /*
     1 req / 30 = 0,033 sec
     5 req / 30 = 0,166 sec
    10 req / 30 = 0,333 sec
    15 req / 30 = 0,5   sec
    20 req / 30 = 0,666 sec
    25 req / 30 = 0,833 sec
    30 req / 30 = 1     sec
    */

    usleep(intval(floatval($throttle) * 1000000));
}

我在我的前端控制器上使用这个,并将值传递给我的路由方法,但那是另一回事。

总之,如果您使用APC,您可以将东西保持在内存中并且具有很小的内存消耗,因为APC遵循FILO方法。 如果您需要更高的超时时间,您可以考虑使用不基于内存的东西。

顺便说一下:MySQL支持使用MEMORY引擎的表。


sleep()存在的问题:

一个安装了PHP模块的典型Apache Web服务器每个实例会消耗约10MB的RAM,为避免超过可用RAM,您可以配置一些Apache设置来限制Apache能够启动的最大实例数。

问题在于当您使用sleep()函数时,该实例仍然处于活动状态,如果有足够多的请求,可能会导致占用所有可用的插槽以启动新的服务器,从而使您的网站不可访问,直到某些挂起的请求完成为止。

据我所知,没有办法从PHP中解决这个问题,因此最终取决于您。


原则上,系统级限流的原理是相同的:
function systemWide($ttl = 86400, $exit = 360)
{
    if (extension_loaded('apc') === true)
    {
        $key = array(__FUNCTION__);

        if (apc_exists(__FUNCTION__) !== true)
        {
            apc_store(__FUNCTION__, 0, $ttl);
        }

        $result = apc_inc(__FUNCTION__, 1);

        if ($result < $exit)
        {
            return ($result / $ttl);
        }

        return true;
    }

    return false;
}

据我所知,这是针对每个用户的限流,是吗?如果是这样,您建议我如何实现系统范围的限流以防止分布式攻击? - RS7
2
问题在于,即使您不使用1秒睡眠来消耗CPU时间,您仍然会将PHP进程绑定在那里。对于大多数前端服务器而言,这将迅速恶化为使整个服务器崩溃的情况(类似于slashdot效应,但只有一个用户而不是大量用户)。 - ircmaxell
@ircmaxell:我知道,但只有在你想要的情况下才会使用 sleep() - Alix Axel
@RS7:这是按IP限流。我不太确定您所说的系统范围限流是什么意思,但如果我理解正确,使用apc_inc()递增单个键就可以为整个系统完成工作。 - Alix Axel
@ircmaxell:你能给我提些建议吗?谢谢。 - RS7
@alix:非常感谢你迄今为止的帮助!我所指的系统范围是,我将检查暴力攻击不仅限于一个人,一个IP地址,而且还要检查分布式暴力攻击,具有不同的IP地址。这样,即使他们使用多个IP地址并在每个IP地址上空出请求,我也能够防止或使攻击复杂化。这是我在Jens Roland的文章中阅读到的某些内容。再次感谢。 - RS7

2

将登录失败的尝试记录在类似于这样的表格中:

FailedLogins
id
timestamp
ip

每次用户尝试登录时,您都会检查该用户的IP地址在过去Y秒内的失败登录尝试次数是否达到X次。
如果用户在Y秒内失败了X次,则会显示错误消息或验证码。

为什么要有 id 列? - Kosta Kontos

1

MySQL数据库能够处理大量的请求/秒,因此如果您没有成千上万的用户,就不必担心瓶颈。

您还可以使用sleep()。请注意:PHP处理的用户比ASP.NET更多-所以再次强调,如果您没有成千上万的用户,您可以使用sleep方法而无需担心瓶颈。

我通常的做法是存储登录尝试(IP、 userID和时间戳)。将其存储在一个表中,在您觉得需要时重置该表(到达一定大小或一天的特定时间)。如果userID + IP在“某段时间内”有超过“登录尝试次数”,则将用户重定向到一个页面,告诉用户他/她已经尝试了太多次并且在接下来的15分钟内无法登录。有点像Windows吧,但它非常有效 :)


@user622470:MySQL的问题在于它不需要拥有成千上万的用户,只需要一个知道如何快速建立数千个连接的用户。 - Alix Axel
1
你的攻击者没有访问你的MySQL的权限,只能访问你的网页。 - hogni89

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接