【问题标题】:How to limit the number of user requests made within a minute如何限制一分钟内发出的用户请求数
【发布时间】:2016-04-01 05:33:45
【问题描述】:

用户将通过诸如script.php?userid=222 之类的 URL 按编号请求文件。此示例将显示文件 #222 的记录。

现在我想将每个(远程 IP)用户的文件数限制为一分钟内最多 5 条不同的记录。但是,用户应该能够多次访问同一个 id 记录。

所以用户可以访问文件 #222 任意次数,但如果(远程 IP)用户在一分钟内访问超过 5 条其他不同的记录,那么它应该会显示错误。

例如,假设在一分钟内发出以下请求:

script.php?userid=222
script.php?userid=523
script.php?userid=665
script.php?userid=852
script.php?userid=132
script.php?userid=002

然后在最后一次请求时它应该显示错误消息。

这是基本代码:

$id = $_GET['userid'];
if (!isset($_GET['userid']) || empty($_GET['userid'])) {
    echo "Please enter the userid";
    die();
}

if (file_exists($userid.".txt") &&
        (filemtime($userid.".txt") > (time() - 3600 * $ttime ))) {
    $ffile = file_get_contents($userid.".txt");} else {
    $dcurl = curl_init();
    $ffile = fopen($userid.".txt", "w+");
    curl_setopt($dcurl, CURLOPT_URL,"http://remoteserver.com/data/$userid");
    curl_setopt($dcurl, CURLOPT_RETURNTRANSFER, TRUE);
    curl_setopt($dcurl, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_0);
    curl_setopt($dcurl, CURLOPT_TIMEOUT, 50);
    curl_setopt($dcurl, CURLOPT_FILE, $ffile);
    $ffile = curl_exec($dcurl); 
    if(curl_errno($dcurl)) // check for execution errors
    {
        echo 'Script error: ' . curl_error($dcurl);
        exit;
    }
    curl_close($dcurl);
    $ffile = file_get_contents($userid.".txt");
}

【问题讨论】:

  • 最可靠的方法是将IP地址与访问的文件ID和日期时间字段一起保存在某处(如文件或数据库)。但是,如果您有很多用户,这将产生相当多的服务器流量和 mysql 服务器上的沉重负载。具有超时的 cookie 或会话将占用更少的资源。但它们也很容易规避。 (删除 cookie 或关闭浏览器以销毁会话)。我想你必须首先在可靠性和可扩展性之间做出选择。
  • 我在这里说最可靠,因为现在你不能真正依赖IP地址。 Tor 之类的浏览器可以在 5 秒内为您提供一个新的 IP 地址,只需按下一个按钮。所以我会先试探性地问自己这是否真的值得麻烦?

标签: php url ip limit


【解决方案1】:

您可以使用会话机制,而不是依赖 IP 地址。您可以通过session_start() 创建会话范围,然后存储与同一用户会话相关的信息。

然后,我建议在此会话范围内保留用户之前发出的请求中使用的唯一 ID 列表,以及请求的时间,忽略任何重复的请求,这些请求始终是允许的。一旦此列表包含 5 个在最后一分钟内带有时间戳的元素并且请求了新 ID,您就会显示错误并拒绝查找。

这是执行此操作的代码。您应该在检查了 userid 参数的存在之后,并且在检索文件内容之前将其放置:

// set the variables that define the limits:
$min_time = 60; // seconds
$max_requests = 5;

// Make sure we have a session scope
session_start();

// Create our requests array in session scope if it does not yet exist
if (!isset($_SESSION['requests'])) {
    $_SESSION['requests'] = [];
}

// Create a shortcut variable for this array (just for shorter & faster code)
$requests = &$_SESSION['requests'];

$countRecent = 0;
$repeat = false;
foreach($requests as $request) {
    // See if the current request was made before
    if ($request["userid"] == $id) {
        $repeat = true;
    }
    // Count (only) new requests made in last minute
    if ($request["time"] >= time() - $min_time) {
        $countRecent++;
    }
}

// Only if this is a new request...
if (!$repeat) {
    // Check if limit is crossed.
    // NB: Refused requests are not added to the log.
    if ($countRecent >= $max_requests) {
        die("Too many new ID requests in a short time");
    }   
    // Add current request to the log.
    $countRecent++;
    $requests[] = ["time" => time(), "userid" => $id];
}

// Debugging code, can be removed later:
echo  count($requests) . " unique ID requests, of which $countRecent in last minute.<br>"; 

// if execution gets here, then proceed with file content lookup as you have it.

已删除会话 cookie...

会话由客户端上的 cookie 维护。用户可以删除此类 cookie,从而获得一个新会话,这将允许用户提出新请求,而无需考虑先前请求的内容。

解决此问题的一种方法是为每个新会话引入一个冷却期。例如,您可以让他们等待 10 秒,然后才能发出第一个请求。为此,请在上面的代码中替换:

if (!isset($_SESSION['requests'])) {
    $_SESSION['requests'] = [];
}

作者:

$initial_delay = 10; // 10 seconds delay for new sessions
if (!isset($_SESSION['requests'])) {
    $_SESSION['requests'] = array_fill(0, $max_requests,
        ["userid" => 0, "time" => time()-$min_time+$initial_delay] 
    );
}

这当然对用户不太友好,因为它会影响任何新会话,也影响那些不试图通过删除 cookie 来作弊的用户。

注册

更好的方法是只允许注册用户使用查找服务。为此,您必须提供用户数据库和身份验证系统(例如基于密码)。请求应记录在数据库中,由用户 ID 键入。如果随后新会话开始,则用户必须首先再次进行身份验证,并且一旦通过身份验证,就会从数据库中检索请求历史记录。这样,用户就无法通过更改客户端配置(IP 地址、cookie、并行使用多个设备……)来欺骗它。

【讨论】:

  • 非常感谢,它就像一个魅力,太棒了!你是大师,你解决了我的问题!提前祝您新年快乐,圣诞快乐。
  • 会话由 cookie 识别。如果有人删除了 cookie,或者不存储它,那么他可以发送无限数量的请求。
  • @BojanHrnkas,绝对!我添加了一个关于这个主题的段落和一个需要身份验证的建议。
  • @trincot 保护注册本身怎么样?对于 UX,我想在注册后直接让用户登录,但如果用户存在或不存在,它会给出反馈。
  • 这可能是要问的问题。继续并发布一个问题。
【解决方案2】:
<?php 
// session_start();
// session_destroy();
// exit;
echo index();
function index()
{
    $id = rand(000,020);
    $min_time = 60;
    $max_requests = 5;
    // $id = 0;
    session_start();
    $repeat = false;
    
    if(!isset($_SESSION["countRecent"]) && !isset($_SESSION["countRecent"]) && !isset($_SESSION["countRecent"])){
        $_SESSION["countRecent"] = 1;
        $_SESSION["time"] = time();
        $_SESSION['userid'][] = $id;
    }else{
        if ($_SESSION["countRecent"] >= $max_requests) {
            if(!in_array($id,$_SESSION['userid'])){
                if ($_SESSION["time"] <= time() - $min_time) {
                    $_SESSION["countRecent"] = 1;
                    $_SESSION["time"] = time();
                }else{
                    return("Too many requests in a short time wait ". ( $_SESSION["time"] - (time() - $min_time)  )). " Seconds";
                }
            }
        }else{
            if(!in_array($id,$_SESSION['userid'])){
                $_SESSION["countRecent"] = $_SESSION["countRecent"] + 1;
                $_SESSION['userid'][] = $id;
            }
        }
    }
    return "Your Result goes here.. id: $id  Counts: ". $_SESSION["countRecent"];
}

试试这个。 快速内存使用率低

但不安全;

也使用数据库

<?php 
$conn = mysqli_connect("localhost", "root", "","db_name") or die("Could not connect database");
$id = rand(0,99);
// $id = 100;
echo index($id);
function index($id,$user=1,$ip='192.168.0.10',$max_requests = 5,$min_time = 20)
{
    global $conn;
    $time = time();
    $req = "INSERT INTO `limit_api_by_ip2`(`id`, `ip`, `time`, `user`, `req`) 
    VALUES (null,INET_ATON('$ip'),'$time','$user',1)
    ON DUPLICATE KEY UPDATE req=req+1;";
    
    $req2 = "INSERT INTO `limit_api_by_ip2`(`id`, `ip`, `time`, `user`, `req`) 
    VALUES (null,INET_ATON('$ip'),'$time','$user',1)
    ON DUPLICATE KEY UPDATE req=1,`time`='".time()."' ;";
    
    $reqid = "INSERT INTO `limit_api_by_ip2_count`(`id`, `user`, `ids`) VALUES (null,'$user',$id)";
    
    $getid = "SELECT `ids` FROM `limit_api_by_ip2_count` WHERE user = $user and ids = $id limit 1;";
    
    $gettime = "SELECT `time`,`req` FROM `limit_api_by_ip2` WHERE user = $user and ip = INET_ATON('$ip') limit 1;";
    // $id = 0;
    $q = mysqli_query($conn,$getid);
    $c = mysqli_num_rows($q);
    if($c==0){
        $get_time = mysqli_query($conn,$gettime);
        $c1 = mysqli_num_rows($get_time);
        if($c1==0){
            mysqli_query($conn,$req);
            mysqli_query($conn,$reqid);
        }else{
            $row = mysqli_fetch_row($get_time);
            
            if ($row[1] >= $max_requests) {
                if ($row[0] <= (time() - $min_time)) {
                    mysqli_query($conn,$req2);
                    mysqli_query($conn,$reqid);
                }else{
                    return "Too many requests in a short time wait ".($row[0]-(time() - $min_time))." Seconds";
                }
            }else{
                mysqli_query($conn,$req);
                mysqli_query($conn,$reqid);
            }
        }

    }else{
        
    }
    if(isset($row[1]))
    {
        $cc = "Counts: ".$row[1];
        $dd = "new id: $id";
    }else{
        $cc = '';
        $dd = "old id: $id";
    }
    return "Your Result goes here.. $dd  ".$cc; 
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-08-05
    • 1970-01-01
    • 2012-02-02
    • 1970-01-01
    • 1970-01-01
    • 2015-07-13
    相关资源
    最近更新 更多