【问题标题】:How to prevent user re-sending the form data?如何防止用户重新发送表单数据?
【发布时间】:2011-10-25 23:50:04
【问题描述】:

我有一个POST 表单,它确实将数据发送到同一个文件,如果使用他浏览器中的后退按钮,他可以轻松地重新发送数据,它仍然会被读取.

有什么办法可以避免这种行为?

【问题讨论】:

  • 不是完全重复,但答案是一样的:stackoverflow.com/questions/4327236/…
  • @LeviMorrison 是的,我之前搜索过一点,发现相同,但我真的不知道如何在那里接受答案的技巧。
  • @LeviMorrison 你能告诉我你为什么低估了我的问题吗?
  • @Cyclone 可能是因为这里之前已经回答过了(我猜测他的观点,而不是我的观点)。

标签: php forms


【解决方案1】:

有两种通用方法。

POST-REDIRECT-GET

首先是post-redirect-get 模式,用户在signup.php 上填写表格,得到POSTed 到submit.php,成功完成后将REDIRECT 发送回thanks.php。浏览器看到重定向响应并为thanks.php 发出一个GET。这是有效的,因为如果没有重定向,点击刷新将重新加载 submit.php 导致重新发布表单数据警告和 POST 请求被双重发出。重定向解决了这个问题。

signup.php
-----------
...
<input type="text" name="email">
...

submit.php
----------
...
if ($_POST) {
  // process data
  header('Location: thanks.php');
}
...

thanks.php
----------
...
Thanks
...

随机数

第二种方法是在表单中嵌入一个nonce 键。这种方法还具有防止 CSRF 攻击的额外好处。这里的方法是在你的表单中注入一个隐藏的 guid:

<input type="nonce" value="<?= uniqid(); ?>">

服务器将需要跟踪所有 nonce 键问题(在数据库或其他东西中),当它收到表单时,它将处理表单并从数据库中删除 nonce 键。如果用户第二次重新提交表单,nonce 键将不存在,服务器可以通过忽略或发出警告来处理。

【讨论】:

    【解决方案2】:

    Levi 发送的链接将为您解答。但如果你想要一个替代方案,我就是这样做的......

    用户在课程中发帖,例如您的课程。同一个文件。在课程开始时,我会进行后期处理。对于这个例子,我将使它变得非常简单......

    <?php
    session_start();
    
    //set form vars ahead of time so you can pre-populate the value attr on post
    $form = array(
        'name' => '',
        'email' => ''
    );
    
    if(!empty($_POST))
    {
        //do some kind of validation...
        $errors = array();
        if(trim($_POST['name']) == '')
            $errors[] = 'Please enter your name';
    
        if(empty($errors))
        {
            $_SESSION['message'] = 'Thank you for participating';
            header('location: /form.php'); // same file
            exit;
        }
        else
        {
            // set the form vars to the post vars so you don't lose the user's input
            $form['name'] = $_POST['name'];
            $form['email'] = $_POST['email'];
    
            $message = '<span style="color:red">';
            foreach($errors AS $error)
            {
                $message .= $error."<br />";
            }
            $message .= '</span>';
            $_SESSION['message'] = $message;
        }
    }
    
    if(isset($_SESSION['message']))
    {
        echo $_SESSION['message'];
        unset($_SESSION['message']);
    }
    ?>
    <form id="some_form" action="" method="post">
        <fieldset>
            <label for="name">Name</label> <input type="text" name="name" value="<?php echo $form['name']; ?>" />
            <br /><br />
            <label for="email">Email</label> <input type="text" name="email" value="<?php echo $form['email']; ?>" />
            <br /><br />
            <input type="submit" name="submit" value="Submit" />
        </fieldset>
    </form>
    

    现在您可以一遍又一遍地刷新,而不是提交两次表单。

    已编辑以显示更多示例。显然,您的验证和错误处理应该比这更复杂一些,但这应该会让您朝着正确的方向前进。

    【讨论】:

    • 很好 :) 谢谢,但它可以保存吗?:s 据我所知,我对安全性知之甚少。
    • 你的意思是安全吗?这么说吧——浏览器窗口是一个单一的进程。一次需要一页。 $_POST 操作将重新加载带有帖子数据的页面——这是我们的脚本进入的地方。一旦它到达该标题行,它就会终止该进程并加载新页面,该页面没有传递任何帖子数据。你应该绝对安全。
    • 哦,还有一件事:如果我想检查 $_POST 中的错误然后显示它们怎么办?我不能,因为标题会破坏整个输出。
    • 只有在验证是干净的情况下才能进入标题路径。如果没有,请按预期加载页面,除了使用帖子变量预先填充表单,这样用户就不会生气并显示错误,无论您选择什么...我将修改答案以反映这一点...
    【解决方案3】:

    如果用户想要这样做,你不能 100% 阻止这种情况,但是为了避免这种情况意外发生,请查看 Post/Redirect/Get

    【讨论】:

    • 它可能关注的对象:如果您不赞成我的回答,请告诉我原因,以便我可以改进。
    • 嗯,反对这个问题的不是同一个人,因为那是我。
    • @LeviMorrison 你不应该低估我的问题!
    • 我敢打赌,投反对票的人(不是我)是因为强烈使用“不能 100% 防止这种情况”,并且可能像您说没有办法而不是实际上那样阅读它阅读帖子。
    【解决方案4】:

    当您发送表单时,将一个随机数推入 $_SESSION['stopdupe'] 值,并推入隐藏字段。

    收到表格进行处理时:

    1. 检查$_SESSION['stopdupe'] 是否存在,并与隐藏字段的值匹配。 (如果没有,请忽略该帖子)
    2. 取消设置$_SESSION['stopdupe']
    3. 正常处理表单。

    这样,如果用户按下提交按钮两次,第二个请求将不会被处理。

    另一个有用的技巧是使用表单上的onSubmit javascript 事件来禁用按钮,这样用户就不能轻易地多次提交它。

    【讨论】:

    • 请注意,这可能会导致同一站点的两个标签页同时打开而无法正常工作。
    • 这是真的,@middus。更完整的解决方案是将随机值推送到 $_SESSION['stopdupe'] 数组和隐藏字段中,然后在发布时检查并从数组中删除该值。
    • 这听起来真的很聪明!难怪你称自己为 MrTrick ;)。
    【解决方案5】:

    这对我有用。我使用 smarty,但也可以不使用 smarty。这只是一个想法。根据需要修改它。

    HTML 表单:

    <form action="submit_file.php" method="POST">
    <input type="hidden" name="submit_edit" value="1">
    <input type="text" name="data"/>
    </form>
    

    globals.php中的php

    if (count($_POST) > 0) {
        if (isset($_POST['submit_edit']) && $_POST['submit_edit'] == '1'
                && time() - $_SESSION['last_request_time'] < 100
                && count($_SESSION['last_request']) > 0
                && serialize($_SESSION['last_request']) == serialize($_POST)) {
    
            $oSmarty->assign('display_time_alert', '1');
            unset($_POST['submit_edit']);
            unset($_REQUEST['submit_edit']);
        } else {
            $_SESSION['last_request_time'] = time();
            $_SESSION['last_request'] = $_POST;
        }
    }
    

    这在 head.tpl 中

    {literal}
        <script>
    function showTimeAlert(){
            alert('{/literal}{tr var="Cannot send the same request twice"}{literal}');
        }
        {/literal}
        {if isset($display_time_alert) AND $display_time_alert eq '1'}
        showTimeAlert();
        {/if}
        {literal}
    
    
    
        </script>
    {/literal}
    

    最后是 submit_file.php

    require_once("globals.php");
    
    if(isset($_POST['submit_edit']) && $_POST['submit_edit'] == '1')){
    //record data
    }
    
    $oSmarty->dysplay(some.tpl);//in some.tpl is included head.tpl
    

    【讨论】:

      【解决方案6】:

      在第一页中使用一个会话变量并将值分配给 1。示例:

      <form name="frm1" method="post">
          <?php $_SESSION['resend_chk']=1; ?>
          <input type="text" name="a" />
          <input type="submit" name="submit">
      </form>
      

      在第二页:

      if(isset($_REQUEST['submit'])
      {
          if($_SESSION['resend_chk']==1)
          {
              insert to db OR and any transaction
              $_SESSION['resend_chk']=0;
          }
      }
      

      它将插入一次或事务将在db中发生一次并且会话变量的值会改变,下次我们尝试重新发送数据时它不会执行任何事务,因为会话变量的值已经改变了。

      【讨论】:

        猜你喜欢
        • 2012-03-03
        • 1970-01-01
        • 2013-11-23
        • 1970-01-01
        • 1970-01-01
        • 2019-01-21
        • 1970-01-01
        • 2015-10-30
        • 1970-01-01
        相关资源
        最近更新 更多