【问题标题】:Prevent Double Form Submit using Tokens使用令牌防止双重表单提交
【发布时间】:2013-03-25 23:43:19
【问题描述】:

我试图通过添加令牌隐藏字段来防止用户重复提交论坛。

这就是我到目前为止所做的(在加载论坛之前,我有这段代码来创建一个以当前时间为值的令牌。

$token = time();
setcookie('formToken', $token, time() + 3600);

在我的论坛中,我有一个这样的隐藏输入

<form method="post" action="'.$PHP_SELF.'?action=update">
<input type="hidden" name="token" value="'.$token.'" />
<input type="submit" value="go" />
</form>

现在在我的页面顶部 $action == "update" 我有这个代码

if(isset($_POST)  &&  ($_POST['token'] != $_COOKIE['formToken'])){
    $error_list .= '<li>You can not submit this forum twise.</li>';
} 

如果我按 F5 刷新页面,它会再次提交表单而不显示我的错误。

【问题讨论】:

  • 不是答案,只是评论。为什么不在表单提交后重定向到“表单提交”页面,这样F5就不会重新发布表单? phpbb 完全像这样(尝试发布一些东西并按 F5 - 不走运)。或者你可以使用 Ajax...
  • 所以用户在屏幕上看到了第一次提交的结果。
  • @DyllenJamesOwens 我的编辑没有名气。
  • @Mike:这和我说的并不矛盾。
  • @Mike 这将防止在刷新时重新提交,因为这将是一个完全不同的页面。

标签: php


【解决方案1】:

我建议你使用PRG pattern(Post/Redirect/Get),这也是phpbb等论坛实现的。

Post/Redirect/Get (PRG) 是一种 Web 开发设计模式, 防止一些重复的表单提交,创建更直观的 用户代理(用户)的接口。 PRG 实现书签和 以可预测的方式刷新按钮,不会产生重复 表单提交。

【讨论】:

  • 我很困惑。我应该在肖恩后面实现 ajax/jQuery 提交吗?或者当用户点击提交按钮时将他们重定向到发布帖子的页面,然后将他们重定向回他们开始的地方?
  • 发布到通过 header("Location: something"); 进行重定向的页面 #1到第 2 页。第 2 页将显示提交过程的结果(如何?取决于您的逻辑以及数据存储和检索的位置/方式)。如果用户刷新页面#2,结果将再次显示而不重新发布。
  • 它会防止“一些”重复是的,但是如果您的用户点击和重定向之间有任何延迟,如果没有其他措施,这个“模式”就会变得毫无用处。
  • 用 $_SESSION(会话)赞美 PRG 模式。 1.检查会话是否存在 2.如果是,阻止用户提交,如果没有,在你做任何处理之前创建会话,所以如果发生双/三/等等..点击,至少你的数据不会获得双重处理 3. 当所有进程完成时取消设置会话
  • 但这并不能保护您免受用户按下浏览器的“返回”按钮并再次提交,对吧?
【解决方案2】:

gd1 answer 不会阻止在复杂的 javascript 表单代码上通过各种 jQuery 绑定双击提交或意外双重提交。

双击可能比禁用提交按钮或使用 javascript 隐藏它更快,所以这也不是一个完整的答案。

会话令牌也不起作用,因为会话尚未写入,因此对于第二个进程可用或更新,这可能只是几毫秒后共享相同的会话 ID。仅在完成第一个过程时才存储会话。

Cookie 技术可能是一个解决方案,只要两个进程都能够通过 cookie 以阻塞方式进行通信,这可能会导致与上述会话共享相同的问题。

最好的解决方案是使用服务器的共享内存访问来检查其他进程是否已经使用预生成的数据哈希处理了数据(订单、支付等),或者使用数据库表阻塞选择和插入来检查如果预生成的哈希已经提交。

【讨论】:

    【解决方案3】:

    为什么不直接在表单提交成功的时候设置一个会话呢?

    所以$_SESSION['submitted'] = 1;

    然后你就可以检查了。

    或者做

    if(isset($_POST['submit'])  &&  ($_POST['token'] != $_COOKIE['formToken'])){
        $error_list .= '<li>You can not submit this forum twice.</li>';
    }
    

    【讨论】:

      【解决方案4】:

      建议 1)

      提交成功后删除cookies(removeTokens)

          function removeToken()
      {
          //set formToken cookie val to "" (or any default xxxx) and the past expiry date for it 
          setcookie("formToken", "", time()-3600);
          //try to unset - this is not needed ,we may try it
          unset($_COOKIE['formToken']);
      }
      

      就是在你的页面上 if(isset($_POST)) removeToken();

      建议 2)

      按照 Tom Wright 的建议在此处执行重定向 Avoiding form resubmit in php when pressing f5

       header('Location: formsubmitSucess.php');
      

      【讨论】:

        【解决方案5】:

        我使用这种方法来防止重复提交表单,到目前为止它在所有场合都有效。如果您需要其他问题,请告诉我,因为本教程假设您具有数据库和 PHP 的中级知识。

        第 1 步:在您的数据库中添加一个字段,如下所示: 将 YOUR-TABLE 替换为数据库表的名称。

            ALTER TABLE `YOUR-TABLE` ADD `token` VARCHAR(35) NULL DEFAULT NULL AFTER    `creationtoken`, ADD UNIQUE (`token`) ;
        

        在表单页面的第 2 步中,您将其添加到第一行: 它将创建一个唯一的令牌,该令牌将与您的查询一起插入到您的数据库表中,以便以后可以检查它以确保没有其他类似的令牌提交到您的数据库中,这意味着没有双重表单提交。

            <?php 
            session_start(); 
            date_default_timezone_set('America/Chicago');
            $_SESSION['token'] = md5(session_id() . time());
            ?>
        

        然后在提交按钮之前添加:

            // add this before the submit button
            // this will post the unique token to the processing page.
        
            <div style="width:100%; color:#C00; font-weight:normal;">Session Token: <?php echo strtolower($_SESSION['token']) ?></div>     
        
            <input type="hidden" name="token" id="token" value="<?php echo  $_SESSION['token']?>" />
            // add this before the submit button
        
        
            <input type="submit" id="submit" name="submit" class="button" value="Submit" />
        

        第 3 步:现在在您的 process.php 页面上

            //this is where all of your form processing takes place.
        
            // this is where you call the database
            // if you need the database file let me know...
            include("../common/databaseclass.php");
            $db= new database();
        
            //here the token is posted then the database table is checked and 
            //if the form has already been added it will return a 1 and will 
            //cause the query to die and echo the error message. 
        
            $token = $_POST['token'];
            $query = "SELECT token FROM YOURTABLE WHERE token = '$token' LIMIT 1";
            $result = $db->query($query);
            $num = mysql_num_rows($result);
        
            if ($num>0) {die('your form has already been submitted, thank you');}
        
        
            else {
        
            $host = "localhost";
            $user = "user";
            $pass = "password";
            $db_name = "database";
        
            mysql_connect($host,$user,$pass);
            @mysql_select_db($db_name) or die( "Unable to select database");
        
        
            // table query      
            $sql1="INSERT INTO YOURTABLE (
            `token`,
            `user`,
            `email`,
            `password`,
            `newaccount`,
            `zipcode`,
            `city`,
            `state`,
            `country`,
            `telephone`,
            `creationip`,
            `createdaccount`
             ) 
             VALUES (
             '$token',
             '$username',
             '$email',
             '$password',
             '$newaccount',
             '$zipcode',
             '$city',
             '$state',
             '$country',
             '$phone',
             '$ipadress',
             '$createdaccount'
        
               )";
        
            $db->query($sql1);
            header("location:" http://home.php ");      
        
            }
        

        【讨论】:

          【解决方案6】:

          对于同样的问题,我编写了一个代码来将它用于我自己的东西。它具有 PRG 模式,可以灵活地在同一页面上使用它或与外部 PHP 文件一起使用以进行重定向 - 易于使用且安全,也许这可能会对您有所帮助。

          class unPOSTer {
          
                  private 
                      $post = "KEEP_POST";
          
                  public function __construct(string $name = null) {
                      if (version_compare(PHP_VERSION, "5.4.0") >= 0) {
                          if (session_status() == PHP_SESSION_NONE) {
                              session_start();
                          }
                      } else {
                          if (!$_SESSION) {
                              session_start();
                          }
                      }
                      $this->post = $name;
                  }
          
                  public function unPost() {
                      if (session_status() !== PHP_SESSION_ACTIVE) {
                          session_start();
                      } elseif (strcasecmp($_SERVER["REQUEST_METHOD"],"POST") === 0) {
                          $_SESSION[$this->post] = $_POST;
                          header("Location: " . $_SERVER["PHP_SELF"] . "?" . $_SERVER["QUERY_STRING"]);
                          exit;
                      } elseif (isset($_SESSION[$this->post])) {
                          $_POST = $_SESSION[$this->post];
                      }
                  }
          
                  public function retrieve($data) {
                      if (isset($_SESSION[$this->post])) {
                          $posts = @$_SESSION[$this->post][$data];
                          if (isset($posts)) {
                              return $posts;
                          } else {
                              return null;
                          }
                      } 
                  }
          
                  public function reset() {
                      if (isset($_SESSION[$this->post])) {
                          unset($_SESSION[$this->post]);
                      }
                  }
              }
          

          然后像这样使用它:

          <?php
              require_once "unPOSTer.class.php";
              $unpost = new unPOSTer();
              $unpost->unPost();
          ?>
          <form action='' method=POST>
              <input type=text name=fname value="<?php echo $unpost->retrieve("fname"); ?>" placeholder="First Name">
              <input type=text name=lname value="<?php echo $unpost->retrieve("lname"); ?>" placeholder="Last Name">
              <input type=submit name=send value=Send>
          </form>
          <?php echo $unpost->reset(); ?>
          

          配置不多,如果您愿意,可以在您发送表单数据的每个页面上进行配置。 retrieve() 方法会吐出您发送的数据,以防您可能返回并修复某些问题。随意在我的GitHub 页面上 fork/pull 它,我在那里添加了 2 个演示。

          【讨论】:

            【解决方案7】:

            我遇到了同样的问题,这里有一个简单的解决方法:

            if(!empty($_SESSION['form_token']) && time() - $_SESSION['form_token'] < 3){
                $data['message'] = 'try again later';
                return;
            }
            $_SESSION['form_token'] = time();
            

            在我的例子中,PRG 模式没有任何效果,因为表单同时提交了多次,并且代码没有被执行,也没有保存数据来比较它。

            【讨论】:

              猜你喜欢
              • 2012-04-05
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2011-05-14
              • 1970-01-01
              • 2011-03-12
              相关资源
              最近更新 更多