6

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

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

$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 刷新页面,它会再次提交表单而不显示我的错误。

4

7 回答 7

11

我建议您使用PRG 模式(Post/Redirect/Get),该模式也由phpbb.

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

http://upload.wikimedia.org/wikipedia/commons/3/3c/PostRedirectGet_DoubleSubmitSolution.png

于 2013-03-25T23:58:31.037 回答
3

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

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

然后你可以检查它。

或者做

if(isset($_POST['submit'])  &&  ($_POST['token'] != $_COOKIE['formToken'])){
    $error_list .= '<li>You can not submit this forum twice.</li>';
}
于 2013-03-25T23:59:06.343 回答
3

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

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

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

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

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

于 2014-01-30T10:14:02.197 回答
0

我使用这种方式来防止双重提交,到目前为止它在所有场合都有效。如果您需要其他问题,请告诉我,因为本教程假设您具有数据库和 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 ");      

    }
于 2015-02-21T08:54:34.190 回答
0

建议1)

成功提交时删除 cookie (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 在此处的建议执行重定向,当按 f5 时避免在 php 中重新提交表单

 header('Location: formsubmitSucess.php');
于 2013-03-26T00:07:08.950 回答
0

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

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

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

于 2020-06-04T13:56:54.237 回答
0

对于同样的问题,我编写了一个代码来将它用于我自己的东西。它具有 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 个演示。

于 2017-05-14T11:57:47.067 回答