刷新页面时如何防止重新提交表单(F5 / CTRL + R)


131

我有一个简单的表单,可将​​文本提交到SQL表。问题在于,用户提交文本后,他们可以刷新页面,并且无需再次填写表单即可再次提交数据。提交文本后,我可以将用户重定向到另一个页面,但是我希望用户停留在同一页面上。

我记得读过一些有关为每个用户提供唯一的会话ID,并将其与另一个值进行比较的信息,这些值解决了我遇到的问题,但我忘记了它在哪里。



1
为什么不希望将用户重定向到另一个页面?
亚当

@Adam:因为这样做超出了向服务器发送另一个请求的余地,服务器又将再次从DB获取一些数据。但这是资源的浪费,因为我们已经在处理POST请求时获取了所有必需的数据
Eugen Konkov

在PRG模式中@EugenKonkov,您只需重定向到显示成功消息的页面。无需从数据库进一步获取。
亚当

@Adam:您还可以显示POST通过数据创建的整个记录。在这种情况下,您需要SELECT从数据库获取它。例如,当您创建发票时,会将您重定向到/invoices/53显示整个发票,而不只是显示“成功”
Eugen Konkov

Answers:


95

使用发布/重定向/获取模式。http://zh.wikipedia.org/wiki/邮寄/重定向/获取

在我的网站上,我将在cookie或会话中存储一条消息,在帖子后重定向,读取cookie /会话,然后清除该会话或cookie变量的值。


1
这使得chrome无法用于仅需要职位的开发!
约翰·彼得斯

26
如果您使用PRG模式,那么您实际上离开了页面吗?这不是在不重定向时如何使事情正常工作的问题吗?
亚当

在我看来,如果您重定向到同一页面,则$ _POST被清除。据我了解,这是理想的效果。无论如何,那是我想要的效果。我认为,如果将答案明确表示,答案会更好。
donutguy640

效果很好,我唯一的建议是确保启用严格的错误测试,以便在开发时在本地捕获错误,否则可能会被忽略。
莫里斯

不回答问题。不知道为什么它是公认的答案。
YungGun

124

我还要指出的是,您可以使用Javascript方法window.history.replaceState来防止刷新和后退按钮上的重新提交。

<script>
    if ( window.history.replaceState ) {
        window.history.replaceState( null, null, window.location.href );
    }
</script>

概念证明在这里:https : //dtbaker.net/files/prevent-post-resubmit.php

我仍然建议使用Post / Redirect / Get方法,但这是一个新颖的JS解决方案。


1
谢谢,@ dtbaker,太棒了:)
Gufran Hasan

1
韩国社交协会,这是完美的工作对我来说,它只是必须创造404页避免用户missunderstanding
苔丝许

简直太神奇了。谢谢
user4906240 '19

2
它在safari中不起作用,它更改了href,但仍将数据保留为带有后发请求的提交
Vo Thanh Tung

这对我有用,谢谢!您为什么推荐PRG方法?
iJassar

16

您确实应该使用Post Redirect Get模式来处理此问题,但是如果您因某种原因最终无法使用PRG(例如,表单本身位于include中,则无法进行重定向),则可以对某些请求参数进行哈希处理根据内容创建一个字符串,然后检查您是否尚未发送该字符串。

//create digest of the form submission:

    $messageIdent = md5($_POST['name'] . $_POST['email'] . $_POST['phone'] . $_POST['comment']);

//and check it against the stored value:

    $sessionMessageIdent = isset($_SESSION['messageIdent'])?$_SESSION['messageIdent']:'';

    if($messageIdent!=$sessionMessageIdent){//if its different:          
        //save the session var:
            $_SESSION['messageIdent'] = $messageIdent;
        //and...
            do_your_thang();
    } else {
        //you've sent this already!
    }

感谢您的分享,但这似乎不起作用。刷新页面确实确实为我重新提交了表单。也许我缺少什么?
aLearner 2013年

刷新页面将重新提交所有内容,但是我们仅选择处理提交数据(如果它与上次发送的数据不同(我们将其存储在会话var'messageIdent'中))。您是否在“如果messageIdents不同”子句(即“ do_your_thang()”在哪里)中处理表单提交?
Moob

感谢您的回复。我最终只是实现了Post / Redirect / Get模式,所以,我现在不记得是什么使我绊倒了。再次感谢您回圈。
aLearner 2013年

此方法不是要阻止重新提交...而是要检测重新提交,以便您可以更改代码以不“做”任何新提交的操作。换句话说:通过这种方法,您可以检测到“原始”提交并放置您的数据。如果不是原始数据,请勿放置您的数据。要么显示“重复尝试”警告,要么显示“确认”。
TheSatinKnight

为此,您必须通过session_start();在文件开头添加会话来启动会话。w3schools.com/php/php_sessions.asp说:注意:session_start()函数必须是文档中的第一件事。在任何HTML标记之前。
wkille

16

表单提交后,我使用此javascript行来阻止弹出窗口,要求刷新时重新提交表单。

if ( window.history.replaceState ) {
  window.history.replaceState( null, null, window.location.href );
}

只需将这一行放在文件的页脚处即可看到魔术


不能从客户端禁用此版本的版本会很不错,但是它简短,容易,快速...可以做它需要做的事情。保留历史记录,以便用户可以导航而无需重新发送帖子。
—PéterVértényi,

16

您可以通过会话变量阻止表单重新提交。

首先,您必须rand()在文本框和$_SESSION['rand']表单页面上进行设置:

<form action="" method="post">
  <?php
   $rand=rand();
   $_SESSION['rand']=$rand;
  ?>
 <input type="hidden" value="<?php echo $rand; ?>" name="randcheck" />
   Your Form's Other Field 
 <input type="submit" name="submitbtn" value="submit" />
</form>

之后,$_SESSION['rand']使用以下文本框$_POST['randcheck']值进行检查:

if(isset($_POST['submitbtn']) && $_POST['randcheck']==$_SESSION['rand'])
{
    // Your code here
}

3
我们可以<input type="hidden" name="randcheck" id="randcheck" value="<?php echo microtime(); ?>" />改用
Nikolay Bronskiy

是的,我们可以使用microtime()以及time()代替rand(),无论提供不同值的函数或变量都可以使用。但是请确保将值设置为SESSION变量。在这里,SESSION必须与randcheck字段进行检查,并防止重新提交表格。
Savoo

检查session和post vars是否匹配后,不应该清除session变量吗?
降噪

1
@denoise我相信@Savoo的想法是,在$_POST表单数据之后,同一页面将重新加载,以重新设置会话和发布变量。在检查变量是否匹配之后,还应该创建变量。因此unset是不需要的。
HalfMens

13

处理表单后,您将重定向到另一个页面:

... process complete....
header('Location: thankyou.php');

您也可以重定向到同一页面。

如果您正在执行诸如注释之类的操作并且希望用户停留在同一页面上,则可以使用Ajax来处理表单提交


12

我找到了下一个解决方法。您可以POST通过处理history对象处理请求后转义重定向。

因此,您具有HTML表单:

<form method=POST action='/process.php'>
 <input type=submit value=OK>
</form>

在服务器上处理此表单时,您无需像这样/the/result/page设置Location标头来将用户重定向到:

$cat process.php
<?php 
     process POST data here
     ... 
     header('Location: /the/result/page');
     exit();
?>

在此处输入图片说明

处理完POSTed数据后,您将得到较小<script>的结果/the/result/page

<?php 
     process POST data here
     render the <script>         // see below
     render `/the/result/page`   // OK
?>

<script>您应该呈现:

<script>
    window.onload = function() {
        history.replaceState("", "", "/the/result/page");
    }
</script>

结果是:

在此处输入图片说明

如您所见,表单数据已POST编入process.php脚本。
该脚本可通过以下方式一次处理POSTed数据并进行渲染/the/result/page

  1. 没有重定向
  2. POST刷新页面时无数据(F5)
  3. 没有重新POST当您通过浏览器历史记录导航到上一页/下一页

UPD

作为另一种解决方案我问功能要求Mozilla Firefox的团队,以允许用户设置NextPage标头,会像Location头和化妆post/redirect/get模式已经过时了。

简而言之。服务器POST成功处理表单数据后:

  1. 设置NextPage标头,而不是Location
  2. 渲染处理POST表单数据的结果,就像处理模式中的GET请求一样post/redirect/get

浏览器依次看到NextPage标题时:

  1. 调整window.locationNextPage价值
  2. 当用户刷新页面时,浏览器将协商GET请求NextPage而不是重新POST格式化数据

我认为,如果实施,效果会很好,不是吗? =)


11
  1. 使用标题并重定向页面。

    header("Location:your_page.php"); 您可以重定向到同一页面或不同页面。

  2. 将$ _POST插入数据库后,取消设置。

    unset($_POST);


54
取消设置$ _POST根本不影响重新提交表单,至少在Chrome中不起作用。
加文2014年

2
不起作用。当用户返回上一页时,它将再次执行POST变量的设置。
桑度,2013年

使用的原因是unset什么?请在回答中添加一些解释,以便其他人可以从中学习
Nico Haase

7

一个很确定的方法是在帖子中实现一个唯一的ID,并将其缓存在

<input type='hidden' name='post_id' value='".createPassword(64)."'>

然后在您的代码中执行以下操作:

if( ($_SESSION['post_id'] != $_POST['post_id']) )
{
    $_SESSION['post_id'] = $_POST['post_id'];
    //do post stuff
} else {
    //normal display
}

function createPassword($length)
{
    $chars = "abcdefghijkmnopqrstuvwxyz023456789";
    srand((double)microtime()*1000000);
    $i = 0;
    $pass = '' ;

    while ($i <= ($length - 1)) {
        $num = rand() % 33;
        $tmp = substr($chars, $num, 1);
        $pass = $pass . $tmp;
        $i++;
    }
    return $pass;
}

我实际上只是写了类似的东西,但是没有麻烦创建密码,我只是列举了我的表格(例如:步骤1-5),所以如果它们相等,我们很乐意继续前进,否则不要保存到数据库或发送电子邮件。但是,让用户降落在需要他的任何地方。
费尔南多·席尔瓦

1
我认为这是比重新加载页面更好的解决方案,因为发布成功后我会显示一条消息,重新加载页面会删除该消息。这对我有用,您也可以使用uniqid
JulioPopócatl2015年

6

这种方法对我来说效果很好,我认为这是完成这项工作的最简单方法。

总体思路是在提交表单后将用户重定向到其他页面,这将在页面刷新时停止重新提交表单。不过,如果您需要在提交表单后将用户保留在同一页面上,则可以通过多种方式进行操作,但是这里我将描述javascript方法。

JavaScript方法

此方法非常简单,一旦提交表单,便会阻止弹出窗口刷新时要求重新提交表单。只需将这行javascript代码放在文件的页脚处,然后看一下魔术。

<script>
if ( window.history.replaceState ) {
  window.history.replaceState( null, null, window.location.href );
}
</script>


这也对我有帮助
Ankit

4

Java脚本

此方法非常简单,一旦提交表单,便会阻止弹出窗口刷新时要求重新提交表单。只需将这行javascript代码放在文件的页脚处,然后看一下魔术。

<script>
    if ( window.history.replaceState ) {
        window.history.replaceState( null, null, window.location.href );
    }
</script>

3

使用表单数据后,只需将其重定向到同一页面即可。我试过了

header('location:yourpage.php');

4
这只是重复了其他人几年前给出的相同答案。(实际上还有两个!)
尼克·赖斯

如果您重复别人的答案,则至少应在答案中添加一些解释,以使其变得有趣
Nico Haase

3

Moob帖子的精炼版本。创建POST的哈希,将其保存为会话cookie,然后比较每个会话的哈希。

// Optionally Disable browser caching on "Back"
header( 'Cache-Control: no-store, no-cache, must-revalidate' );
header( 'Expires: Sun, 1 Jan 2000 12:00:00 GMT' );
header( 'Last-Modified: ' . gmdate('D, d M Y H:i:s') . 'GMT' );

$post_hash = md5( json_encode( $_POST ) );

if( session_start() )
{
    $post_resubmitted = isset( $_SESSION[ 'post_hash' ] ) && $_SESSION[ 'post_hash' ] == $post_hash;
    $_SESSION[ 'post_hash' ] = $post_hash;
    session_write_close();
}
else
{
    $post_resubmitted = false;
}

if ( $post_resubmitted ) {
  // POST was resubmitted
}
else
{
  // POST was submitted normally
}

2

基本上,您需要重定向到该页面之外,但是当您的Internet速度较慢时,它仍然可能会引起问题(从服务器端重定向标题)

基本方案示例:

单击提交按钮两次

解决方式

  • 客户端

  • 服务器端

    • 发送请求时使用基于区别的哈希时间戳/时间戳。
    • Userequest令牌。当主负载分配时,分配一个临时请求令牌,如果重复该令牌将被忽略。

2

如何防止未经重定向的php表单重新提交。如果使用$ _SESSION(在session_start之后)和$ _POST表单,则可以执行以下操作:

if ( !empty($_SESSION['act']) && !empty($_POST['act']) && $_POST['act'] == $_SESSION['act'] ) {
  // do your stuff, save data into database, etc
}

在您的html表单中输入以下内容:

<input type="hidden" id="act" name="act" value="<?php echo ( empty($_POST['act']) || $_POST['act']==2 )? 1 : 2; ?>">
<?php
if ( $_POST['act'] == $_SESSION['act'] ){
    if ( empty( $_SESSION['act'] ) || $_SESSION['act'] == 2 ){
        $_SESSION['act'] = 1;
    } else {
        $_SESSION['act'] = 2;
    }
}
?>

因此,每次提交表单时,都会生成一个新动作,将其存储在会话中并与后动作进行比较。

附:如果您使用的是Get表单,则可以轻松地使用GET更改所有POST,它也可以使用。


1

将其插入数据库后,调用unset()方法清除数据。

未设置($ _POST);

为防止刷新数据插入,请在插入记录后将页面重定向到同一页面或不同页面。

header('Location:'。$ _ SERVER ['PHP_SELF']);


1
请在您的答案中添加一些解释,以便其他人可以从中学习-为什么unset需要该电话?如果您跳过该怎么办?
Nico Haase

0

使用Keverw回答中的Post / Redirect / Get模式是一个好主意。但是,您无法停留在页面上(我想这就是您要的内容吗?)此外,有时它可能会失败

如果网络用户由于服务器延迟而在完成首次提交之前刷新,则导致某些用户代理中的HTTP POST请求重复。

另一个选择是将文本存储到会话中,如下所示:

if($_SERVER['REQUEST_METHOD'] != 'POST')
{
  $_SESSION['writeSQL'] = true;
}
else
{
  if(isset($_SESSION['writeSQL']) && $_SESSION['writeSQL'])
  {
    $_SESSION['writeSQL'] = false;

    /* save $_POST values into SQL */
  }
}

0

正如其他人所说,不可能不使用post / redirect / get。但是同时,在服务器端执行您想做的事情也很容易。

在POST页面中,您仅验证用户输入但不对其进行操作,而是将其复制到SESSION数组中。然后,您再次重定向回主提交页面。您的主要提交页面首先检查您正在使用的SESSION数组是否存在,如果存在,则将其复制到本地数组中并取消设置。从那里可以采取行动。

这样,您只需要一次完成所有主要工作即可实现您想要的工作。


0

之后,我在大型项目中寻找解决方案以防止重新提交。该代码可与$ _GET和$ _POST高度配合,并且我不能更改表单元素的行为,否则会出现无法预料的错误。所以,这是我的代码:

<!-- language: lang-php -->
<?php

// Very top of your code:

// Start session:
session_start();

// If Post Form Data send and no File Upload
if ( empty( $_FILES ) && ! empty( $_POST ) ) {
    // Store Post Form Data in Session Variable
    $_SESSION["POST"] = $_POST;
    // Reload Page if there were no outputs
    if ( ! headers_sent() ) {
        // Build URL to reload with GET Parameters
        // Change https to http if your site has no ssl
        $location = "https://" . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI'];
        // Reload Page
        header( "location: " . $location, true, 303 );
        // Stop any further progress
        die();
    }
}

// Rebuilt POST Form Data from Session Variable
if ( isset( $_SESSION["POST"] ) ) {
    $_POST = $_SESSION["POST"];
    // Tell PHP that POST is sent
    $_SERVER['REQUEST_METHOD'] = 'POST';
}

// Your code:
?><html>
    <head>
        <title>GET/POST Resubmit</title>
    </head>
    <body>

    <h1>Forms:</h1>
    <h2>GET Form:</h2>
    <form action="index.php" method="get">
        <input type="text" id="text_get" value="test text get" name="text_get"/>
        <input type="submit" value="submit">
    </form>
    <h2>POST Form:</h2>
    <form action="index.php" method="post">
        <input type="text" id="text_post" value="test text post" name="text_post"/>
        <input type="submit" value="submit">
    </form>
    <h2>POST Form with GET action:</h2>
    <form action="index.php?text_get2=getwithpost" method="post">
        <input type="text" id="text_post2" value="test text get post" name="text_post2"/>
        <input type="submit" value="submit">
    </form>
    <h2>File Upload Form:</h2>
    <form action="index.php" method="post" enctype="multipart/form-data">
        <input type="file" id="file" name="file">
        <input type="submit" value="submit">
    </form>

    <h1>Results:</h1>
    <h2>GET Form Result:</h2>
    <p>text_get: <?php echo $_GET["text_get"]; ?></p>
    <h2>POST Form Result:</h2>
    <p>text_post: <?php echo $_POST["text_post"]; ?></p>
    <h2>POST Form with GET Result:</h2>
    <p>text_get2: <?php echo $_GET["text_get2"]; ?></p>
    <p>text_post2: <?php echo $_POST["text_post2"]; ?></p>
    <h2>File Upload:</h2>
    <p>file:
    <pre><?php if ( ! empty( $_FILES ) ) {
            echo print_r( $_FILES, true );
        } ?></pre>
    </p>
    <p></p>
    </body>
    </html><?php
// Very Bottom of your code:
// Kill Post Form Data Session Variable, so User can reload the Page without sending post data twice
unset( $_SESSION["POST"] );

它只能避免$ _POST而不是$ _GET的重新提交。但这是我需要的行为。重新提交问题不适用于文件上传!


0

对我有用的是:

if ( !refreshed()) {
   //Your Submit Here
        if (isset( $_GET['refresh'])) {
            setcookie("refresh",$_GET['refresh'], time() + (86400 * 5), "/");
        }

    }    
}


function refreshed()
{
    if (isset($_GET['refresh'])) {
        $token = $_GET['refresh'];
        if (isset($_COOKIE['refresh'])) {
            if ($_COOKIE['refresh'] != $token) {
                return false;
            } else {
                return true;
            }
        } else {
            return false;
        }
    } else {
        return false;
    }
}  


function createToken($length) {
    $characters = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
    $charactersLength = strlen($characters);
    $randomString = '';
    for ($i = 0; $i < $length; $i++) {
        $randomString .= $characters[rand(0, $charactersLength - 1)];
    }
    return $randomString;
}

?>

并以您的形式

 <form  action="?refresh=<?php echo createToken(3)?>">



 </form>

-2
if (($_SERVER['REQUEST_METHOD'] == 'POST') and (isset($_SESSION['uniq']))){
    if($everything_fine){
        unset($_SESSION['uniq']);
    }
}
else{
    $_SESSION['uniq'] = uniqid();
}

请在您的答案中添加一些解释,以使其他人可以从中学习-答案$everything_fine从何而来?
Nico Haase

-3

为什么不只是使用$_POST['submit']变量作为逻辑语句来保存表格中的内容。您可以随时重定向到同一页面(在情况下,他们刷新,而当他们打go back在浏览器中,后期变数将不再被设定的提交。只要确保你的提交按钮有nameidsubmit


1
看看en.wikipedia.org/wiki/Post/Redirect/Get!这是正确的方法
jan267
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.