刷新页面时如何防止表单重新提交(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:您也可以显示由POST
ing 数据创建的整个记录。在这种情况下,您需要从 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()
的每个文件上启动会话
【讨论】:
我们可以改用<input type="hidden" name="randcheck" id="randcheck" value="<?php echo microtime(); ?>" />
是的,我们可以使用 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();
?>
处理POST
ed 数据后,您会渲染小<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
ed 到 process.php
脚本。
此脚本一次处理POST
ed 数据并渲染/the/result/page
:
-
无重定向
刷新页面(F5)时没有re
POST
数据
当您通过浏览器历史导航到上一页/下一页时,没有回复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)的主要内容,如果未能解决你的问题,请参考以下文章