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

Posted

技术标签:

【中文标题】刷新页面时如何防止表单重新提交(F5 / CTRL+R)【英文标题】:How to prevent form resubmission when page is refreshed (F5 / CTRL+R) 【发布时间】:2020-12-25 05:42:05 【问题描述】:

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

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

【问题讨论】:

Post/Redirect/Get. 为什么不想将用户重定向到另一个页面? @Adam:因为这对于向服务器发出另一个请求是多余的,而服务器又会再次从数据库中获取一些数据。但这是资源浪费,因为我们在处理 POST 请求时已经获取了所有需要的数据 @EugenKonkov 在 PRG 模式中,您只需重定向到显示成功消息的页面。无需进一步从数据库中获取。 @Adam:您也可以显示由POSTing 数据创建的整个记录​​。在这种情况下,您需要从 DB 中 SELECT 它。例如,当您创建发票时,您将被重定向到 /invoices/53,它会显示整个发票,而不仅仅是“成功” 【参考方案1】:

我还想指出,您可以使用 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 解决方案。

【讨论】:

谢谢,@dtbaker,太棒了:) Tks,这对我来说是完美的工作,它只需要创建 404 页面避免用户误解 简直太棒了。谢谢你 它在 safari 中不起作用,它改变了 href 但仍然保留了 post 请求提交的数据 这对我有用,谢谢!为什么推荐 PRG 方法?【参考方案2】:

使用发布/重定向/获取模式。 http://en.wikipedia.org/wiki/Post/Redirect/Get

对于我的网站,我会将消息存储在 cookie 或会话中,在发布后重定向,读取 cookie/会话,然后清除该会话或 cookie 变量的值。

【讨论】:

这使得 chrome 无法用于业务只需要一个帖子的开发! 如果你使用PRG模式,那么你真的离开了页面,对吧?问题不是在不重定向时如何使事情正常工作吗? 在我看来,如果您重定向到同一页面,那么 $_POST 将被清除。据我了解,这是预期的效果。无论如何,那是我想要的效果。不过,我认为如果它明确地给出答案会更好。 效果很好,我唯一的建议是确保启用严格的错误测试,以便在开发时在本地捕获错误,否则它们可能会被忽视。 使用 PRG,你会失去上下文,这对我来说是一个糟糕的解决方案。我们不应该那样做。【参考方案3】:

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

首先,您必须在文本框中设置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

确保在使用 session_start() 的每个文件上启动会话

【讨论】:

我们可以改用&lt;input type="hidden" name="randcheck" id="randcheck" value="&lt;?php echo microtime(); ?&gt;" /&gt; 是的,我们可以使用 microtime() 和 time() 来代替 rand() ,无论我们可以使用任何提供不同值的函数或变量。但请确保将该值设置为 SESSION 变量。这里 SESSION 是必须检查 randcheck 字段并防止重新提交表单。 在检查 session 和 post vars 是否匹配后不应该清除 session 变量吗? @denoise 我相信@Savoo 的想法是,在表单数据的$_POST 之后,相同的页面会重新加载,重新设置会话和发布变量。在检查变量是否匹配后,还应创建变量。因此不需要unset 我目前正在使用这种方法,带有令牌。但是,这会在多选项卡提交时产生问题。由于它将在每个选项卡上创建新的rand(),并覆盖会话值,这意味着打开的旧选项卡不再有效。有什么建议吗?【参考方案4】:

这种方法对我很有效,我认为这是最简单的方法。

一般的想法是在表单提交后将用户重定向到其他页面,这将在页面刷新时停止表单重新提交。尽管如此,如果您需要在提交表单后将用户保持在同一页面上,您可以通过多种方式进行,但这里我描述的是 JavaScript 方法。


JavaScript 方法

这种方法非常简单,一旦提交表单,就会阻止在刷新时要求重新提交表单的弹出窗口。只需将这行 JavaScript 代码放在文件的页脚即可看到“魔法”。

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

</script>

【讨论】:

这对我也有帮助 这在 Safari 中是否已修复? ***.com/a/54799886 公然复制+粘贴以前的答案(见@Mo'men的答案)【参考方案5】:

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

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

只需将这一行放在文件的页脚处,看看它的神奇之处

【讨论】:

一个不能从客户端禁用的版本会很好,但它简短、简单、快速......并且可以做它需要做的事情。保留历史记录,以便用户可以向后导航,而无需重新发送帖子。 这为我解决了这个问题。这种方法有什么缺点吗?【参考方案6】:

您确实应该使用 Post Redirect Get 模式来处理此问题,但如果您以某种方式最终处于 PRG 不可行的位置(例如,表单本身位于包含中,防止重定向),您可以散列一些请求参数根据内容生成一个字符串,然后检查你是否还没有发送它。

//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!
    

【讨论】:

感谢分享,但这似乎不起作用。刷新页面确实会为我重新提交表单。也许我错过了什么? 刷新页面将重新提交所有内容,但我们只选择处理与上次发送的数据不同的提交数据(我们存储在 session var 'messageIdent' 中)。您是否在“if messageIdents are different”子句中处理表单提交(即“do_your_thang()”在哪里)? 感谢您的回复。我最终只是实现了 Post / Redirect / Get 模式,所以,我现在不记得到底是什么绊倒了我。不过,再次感谢您回过头来。 这个方法不是为了阻止重新提交......而是为了检测重新提交,这样你就可以改变你的代码,不要“做”任何你会做的事情,如果这是一个新的提交。换句话说:使用这种方法,您可以检测“原始”提交并放置您的数据。如果它不是原创的,请不要放置您的数据。要么显示“欺骗尝试”警告,要么显示“确认”。 要使这个工作,您必须通过在文件开头添加session_start(); 来启动会话。 w3schools.com/php/php_sessions.asp 说 注意: session_start() 函数必须是文档中的第一件事。在任何 html 标记之前。【参考方案7】:

当表单被处理时,你重定向到另一个页面:

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

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

如果您正在做类似 cmets 的操作,并且希望用户停留在同一页面上,您可以使用 Ajax 来处理表单提交

【讨论】:

【参考方案8】:

我找到了下一个解决方法。在处理POST 请求后,您可以通过操作history 对象来逃避重定向。

所以你有了 HTML 表单:

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

当您在服务器上处理此表单时,您无需通过设置 Location 标头将用户重定向到 /the/result/page,如下所示:

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

处理POSTed 数据后,您会渲染小&lt;script&gt; 和结果/the/result/page

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

你应该渲染的&lt;script&gt;

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

结果是:

如您所见,表单数据是 POSTed 到 process.php 脚本。 此脚本一次处理POSTed 数据并渲染/the/result/page

    无重定向 刷新页面(F5)时没有rePOST数据 当您通过浏览器历史导航到上一页/下一页时,没有回复POST

UPD

作为另一种解决方案,我要求 Mozilla FireFox 团队的feature request 允许用户设置NextPage 标头,该标头将像Location 标头一样工作,并使post/redirect/get 模式过时。

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

    设置NextPage 标头而不是Location 呈现处理POST 表单数据的结果,就像它在post/redirect/get 模式中呈现GET 请求一样

浏览器依次看到NextPage标头:

    NextPage 值调整window.location 当用户刷新页面时,浏览器会将GET请求协商到NextPage而不是rePOST表单数据

我认为如果实施这将是极好的,不是吗? =)

【讨论】:

【参考方案9】:

    使用标题并重定向页面。

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

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

    unset($_POST);

【讨论】:

取消设置 $_POST 根本不会影响表单重新提交,至少在 Chrome 中不会。 这行不通。当用户返回后页时,它会再次执行 POST 变量的设置。 使用unset的原因是什么?请在您的答案中添加一些解释,以便其他人可以从中学习【参考方案10】:

一个非常可靠的方法是在帖子中实现一个唯一的 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),所以如果它们相等我们很高兴继续前进,否则不要保存到数据库或发送电子邮件。但是让用户随心所欲地降落。 我认为这是比重新加载页面更好的解决方案,因为我在发布成功时会显示​​一条消息,并且重新加载页面会删除该消息。这对我有用,你也可以使用 uniqid【参考方案11】:

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

header('location:yourpage.php');

【讨论】:

这只是重复其他人多年前给出的相同答案。 (实际上是另外两个!) 如果你重复别人的答案,你至少应该在你的答案中添加一些解释,让它变得有趣【参考方案12】:

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

【讨论】:

【参考方案13】:

基本上,您需要重定向到该页面之外,但在您的互联网速度较慢时仍然会出现问题(来自服务器端的重定向标头)

基本场景示例:

点击提交按钮两次

解决方法

客户端

一旦客户点击提交按钮就禁用它 如果您使用 Jquery:Jquery.one PRG Pattern

服务器端

在发送请求时使用基于区分的哈希时间戳/时间戳。 用户请求令牌。当主加载时分配一个临时请求令牌,如果重复则忽略。

【讨论】:

【参考方案14】:

如何防止没有重定向的 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;
    

?>

因此,每次提交表单时,都会生成一个新的行为,存储在会话中并与发布行为进行比较。

Ps:如果您使用的是 Get 表单,您可以轻松地使用 GET 更改所有 POST 并且它也可以工作。

【讨论】:

【参考方案15】:

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

未设置($_POST);

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

header('Location:'.$_SERVER['PHP_SELF']);

【讨论】:

请在您的答案中添加一些解释,以便其他人可以从中学习 - 为什么需要unset 电话?如果你跳过它会发生什么?【参考方案16】:

$_POST['submit'] 变量在页面初始加载时不会存在,只有在以下条件为真时才能运行 curl。

if($_POST['submit'] == "submit")

// This is where you run the Curl code and display the output
  $curl = curl_init();



//clear $post variables after posting
$_POST = array();


【讨论】:

【参考方案17】:

使用来自 Keverw 答案的 Post/Redirect/Get 模式是一个好主意。但是,您无法停留在您的页面上(我认为这是您所要求的?)此外,有时可能fail:

如果网络用户在初始提交完成之前刷新 由于服务器滞后,导致重复的 HTTP POST 请求 某些用户代理。

如果文本应该像这样写入 SQL 数据库,另一种选择是存储在会话中:

if($_SERVER['REQUEST_METHOD'] != 'POST')

  $_SESSION['writeSQL'] = true;

else

  if(isset($_SESSION['writeSQL']) && $_SESSION['writeSQL'])
  
    $_SESSION['writeSQL'] = false;

    /* save $_POST values into SQL */
  

【讨论】:

【参考方案18】:

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

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

这样你只做一次你所有的主要工作,实现你想做的事情。

【讨论】:

【参考方案19】:

我在之后的大型项目中寻找防止重新提交的解决方案。 该代码高度适用于 $_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。但这是我需要的行为。 重新提交问题不适用于文件上传!

【讨论】:

【参考方案20】:

对我有用的是:

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>

【讨论】:

【参考方案21】:

form.php 示例展示了如何正确使用 PRG(当表单有效或无效时)。

仅当表单有效并执行操作时才会重定向到同一页面。 重定向可防止表单在页面刷新时重新提交。 当表单有效时,它使用会话不会丢失您想要显示的成功消息。 有两个用于测试的按钮:“有效提交”、“无效提交”。尝试两者并在此之后刷新页面。
<?php
session_start();

function doSelfRedirect()

  header('Location:'.$_SERVER['PHP_SELF']);
  exit;


function setFlashMessage($msg)

  $_SESSION['message'] = $msg;


function getFlashMessage()

  if (!empty($_SESSION['message'])) 
    $msg = $_SESSION['message'];
    unset($_SESSION['message']);
   else 
    $msg = null;
  

  return $msg;


if ($_SERVER['REQUEST_METHOD'] === 'POST') 
  // Validation primitive example.
  if (empty($_POST['valid'])) 
    $formIsValid = false;
    setFlashMessage('Invalid form submit');
   else 
    $formIsValid = true;
  

  if ($formIsValid) 
    // Perform any actions here.
    // ...

    // Cool!
    setFlashMessage('Form is valid. Action performed.');

    // Prevent form resubmission.
    doSelfRedirect();
  

?>
<h1>Hello form</h1>

<?php if ($msg = getFlashMessage()): ?>
  <div><?= $msg ?></div>
<?php endif; ?>

<form method="post">
  <input type="text" name="foo" value="bar"><br><br>
  <button type="submit" name="invalid" value="0">Invalid submit</button>
  <button type="submit" name="valid" value="1">Valid submit</button>
</form>

【讨论】:

【参考方案22】:
if (($_SERVER['REQUEST_METHOD'] == 'POST') and (isset($_SESSION['uniq'])))
    if($everything_fine)
        unset($_SESSION['uniq']);
    

else
    $_SESSION['uniq'] = uniqid();

$everything_fine 是表单验证的布尔结果。如果表单未验证,则通常应再次显示并提示要更正的内容,以便用户可以再次发送。因此,如果需要更正的形式,也会再次创建 $_SESSION['uniq']

【讨论】:

请在您的答案中添加一些解释,以便其他人可以从中学习 - $everything_fine 来自哪里? @NicoHaase $everything_fine 是表单验证的布尔结果。如果表单未验证,则通常应再次显示并提示要更正的内容,以便用户可以再次发送。因此,如果需要更正的表单,也会再次创建 $_SESSION['uniq'] 请通过编辑在您的答案中添加全部解释 @NicoHaase 这不是我的答案,我所做的描述在答案的代码中很明显。【参考方案23】:

为什么不直接使用$_POST['submit'] 变量作为逻辑语句来保存表单中的任何内容。您始终可以重定向到同一页面(如果他们刷新,并且当他们在浏览器中点击go back 时,将不再设置提交帖子变量。只要确保您的提交按钮有一个name 和@987654325 @submit.

【讨论】:

看看en.wikipedia.org/wiki/Post/Redirect/Get!这是正确的方法

以上是关于刷新页面时如何防止表单重新提交(F5 / CTRL+R)的主要内容,如果未能解决你的问题,请参考以下文章

struts2框架之重复提交问题

如何防止“确认表单重新提交”对话框?

防止表格重新提交

F5刷新与Ctrl+F5强制刷新的区别

在 wordpress 中编码时防止双重表单提交

在浏览器中刷新页面而不重新提交表单