【问题标题】:Again, stopping back button form resubmission with nonce再次,使用 nonce 停止返回按钮表单重新提交
【发布时间】:2014-12-09 21:59:15
【问题描述】:

伙计们,我知道这个话题已经死了,但是在阅读了这里的许多回答问题后,我离解决方案还差得远。

问题:提交表单后,即使使用Post-Redirect-Get,用户仍然可以按返回按钮返回到原始表单,这将与提交时完全相同。然后用户只需按下提交按钮即可再次发送相同的数据。

我意识到尝试禁用后退按钮是一种不好的做法,所以我想要: 1.- 能够清除表单,以便在通过后退按钮返回已发布的表单时用户输入的数据不再存在。 2.- 能够在代码中区分原始表单提交和重复表单提交。

我一直在读到您可以使用随机数完成 #2。在Stop data inserting into a database twice 我读到了

对于重新提交存在问题的表单,我使用 nonces(“使用一次的数字”)。创建一个随机数/字符串,将其存储在会话数据中,并将其作为隐藏字段添加到表单中。提交表单时,检查 POSTed 值是否与会话值匹配,然后立即清除会话值。重新提交将失败。 (詹姆斯·索科尔)

看起来很清楚,但我似乎无法让它发挥作用。这是骨架代码,尽可能简短。我已经单步执行了。当我按下后退按钮时,PHP 代码从头开始执行。 $_SERVER['REQUEST_METHOD'] 返回GET 所以好像表单还没有发布,代码落空并生成一个新的随机数。当表单提交(第二次)时,$_SESSION['rnd'] 等于 $_POST['rnd'],因此重新提交的表单会像第一次一样被处理。

<?php
  session_start();
  if ($_SERVER['REQUEST_METHOD'] == 'POST') {
    if ($_SESSION['rnd'] == $_POST['rnd']) {
      $form_data = $_POST; //process form
      $_SESSION['rnd'] = 0;
      $msg = 'data posted';
    }
    else {
      $msg = 'cannot post twice';
    }
  }
  else {
    $msg = 'ready to post';
    $rnd = rand(100,999);
    $_SESSION['rnd'] = $rnd;
  }
?>
<html>
  <head>
    <title>app page</title>
  </head>
  <body>
  <h1>app page</h1>
  <h3><?= $msg ?></h3>
  <form method="post">
    Field 1: <input type="text" name="field1"
      value="<?= (isset($form_data['field1']) ? $form_data['field1'] : '') ?>">
    <input type="submit" name="submit" value="Ok">
    <input type="hidden" name="rnd" value="<?= (isset($rnd) ? $rnd : $_POST['rnd']) ?>">
  </form>

  </body>
</html>

感谢您的任何见解。我意识到上面的代码没有实现 PRG,但我认为没关系。问题不是 F5 刷新,而是后退按钮刷新。

【问题讨论】:

  • 提交后为什么不直接重定向?
  • 重定向可防止用户按 F5 时意外重新提交,但不能防止故意使用后退按钮返回页面并提交新表单而无需填写。也许我应该解释一下应用程序。这适用于教授用来调查学生的调查生成器。他想防止一些恶作剧者填写调查表,然后返回按钮/提交 50 次,从而在调查表中引入大量噪音。但调查是匿名的,因此我们可以针对每个用户进行限制。
  • 一个可能的解决方案可能是js,您只需在提交后重置表单,因此他们无法在返回时检索信息。但我不知道你是否愿意走那条路
  • 将“nonce”保存在数据库而不是会话中可能是值得的。会话在几分钟后超时。 'sqlite' 数据库适用于这类事情。
  • @RyanVincent 是的,毫无疑问,但是在数据库或会话中或只是写入文本文件中,问题仍然存在:当 PHP 脚本开始运行时,如何判断它是否正在运行因为它是从浏览器的 URL 栏中执行的,或者因为用户按下了后退按钮。似乎没有办法做到这一点。

标签: php forms back


【解决方案1】:

您需要添加一些逻辑来设置 $_SESSION['rnd'] 变量,而不仅仅是在表单提交时将其设置为 0。您可能还想设置一个超时时间很长的 cookie。我也在其中添加了 cookie。

  session_start();
  if ($_SERVER['REQUEST_METHOD'] == 'POST') {
    if ($_SESSION['rnd'] == $_POST['rnd']) {
      $form_data = $_POST; //process form
      $_SESSION['rnd'] = "POSTED";
      setcookie("rnd", "POSTED", time()+3600*24*14); //cookie expires in 2 weeks
      $msg = 'data posted';
    } else {
      $msg = 'cannot post twice';
    }
  } else {
    $msg = 'ready to post';
    $rnd = rand(100,999);
    if($_SESSION['rnd'] == "POSTED" || $_COOKIE['rnd'] == "POSTED"){
        $_SESSION['rnd'] = "POSTED"; //set value of session if you're here because the cookie was detected
        $rnd = -100;
    } else {
        $_SESSION['rnd'] = $rnd;
    }
  }

如果您想重置后退按钮上的表单,您可以使用这个非常简单的 javascript 来完成。

<script>
    var posted = document.getElementById("rnd").value();
    if(posted < 0){
        document.getElementById("form").reset();
    }
</script>

您需要将 id 属性“form”添加到表单中才能正常工作

  <form id="form" method="post">

【讨论】:

  • 谢谢!我看到您对代码所做的更改。我会在本地尝试它们。只是为了澄清一下,cookie 是一个好主意,但我是否正确,严格来说,cookie 和会话变量不是实现目标所必需的?此外,javascript 是我最薄弱的环节,那么我会将那个 javascript 代码附加到什么事件?在表单的 onsubmit 事件上?还是太早了?
  • 您说得对,它们并不是实现相同目标所必需的,但在某些情况下,浏览器可能不接受 cookie 但接受会话变量。如果他们已经提交了表格,您只是覆盖了所有基础以重新捕获该人。关于 javascript,当我测试它时,将它放在
【解决方案2】:

这还不是答案 - 但我可以发布积分并删除和更改它们。

RobertSF:我正在努力解决这个问题,因为我必须了解在这些情况下该怎么做。

我已经睡了,正在喝咖啡.... :-)

我认为“浏览器后退按钮”的使用是“红鲱鱼”。

如果我们说明程序的“要求”,那么我们应该能够看到问题是什么以及我们可以做些什么。

1) 一旦数据被验证并从“表单”接受,那么它就变成了“只读”。这并不是什么不寻常的要求。

2) 从表单接受的数据中没有“明显”键,因此我们必须为每个“完整”数据的“表单”生成一个“唯一”键。此外,每个“充满”数据的“表格”都将被视为一个“事件”。

3) 用户可以输入与我们已有的数据相同的数据,但如果在不同的“事件”下输入,则必须接受。

但是,如果我们可以编写一些代码来强制执行这三个要求,那么我们曾经接受过的数据的“浏览器返回按钮和重新提交”应该不是问题。它将被报告为已在数据库中。

那么,我们该怎么做呢?

我们必须生成唯一的“事件键”,也是“充满数据的表单”的键。

我们可以使用“nonce”来做到这一点。

每个“nonce”都有一个“状态”,例如“newRequest”、“hasData”。

我将编写一个满足要求的完整但最小的程序。我们也可以对它进行“结构化”,使其易于理解,因此我将使用“mvc”结构。

我还没有把它写成'mvc'形式。它只需要重组。稍后将其作为答案发布。以下是我之前对这些问题的一些想法。

您发送的每个表单都必须有一个唯一的“nonce”,当稍后可能返回相同的表单时,可以检查它的状态!实际上,有许多可能的“活动”形式“存在”,每个形式都有一个“唯一的随机数”和相关的状态。

这就是我将它们存储在数据库中的原因。

如果你想使用 $_SESSION 那么你必须使用一个'nonce'数组和每个'nonce'的当前状态。

当用户在表单中输入数据并被接受时,状态为:设置为:'hasData'。

【讨论】:

  • 感谢您的光临!我不认为会有多种活动形式,但你的#3 是正确的。如果两个不同的用户提交了两个相同的表单(应用程序是调查),则两个相同的表单提交并不无效,但由于调查是匿名的,我们无法判断两个表单是否由同一用户填写。 但是,调查的时间足够长,没有人会费心填写两个调查。我们只是试图阻止使用返回按钮(是的,返回返回按钮)轻松提交重复表单的恶作剧。
  • 我听到你在说什么,但它不起作用,因为后退按钮的行为会欺骗你为之前提交的表单提供一个新的唯一 ID。当第二次提交之前提交的表单时,它的唯一 ID 与您存档的内容相匹配,因此您处理该表单就像它是一个新表单一样 - 但事实并非如此。
【解决方案3】:

我期待着 Ryan Vincent 的回答,但在此期间,经过一番拉扯,这是我设计的处理这种情况的代码。回顾一下,我们正在讨论防止重复提交表单。然而,这种情况使这个案子有些不同寻常。

该应用程序是对教授在学期开始和结束时要求学生回答的 150 个问题的调查。这些调查是匿名的,学生可以在任何可以访问互联网的地方进行调查,但许多人会在学校的计算机实验室进行调查,在那里进行调查的几个学生可能会聚集在同一台计算机周围。

知道调查网址的任何人都可以参加调查,但我们不担心非学生参加。让学生接受它已经够难的了,但我们不希望学生作为一个恶作剧,能够使用后退按钮返回到所有数据仍然存在的表单并一次又一次地提交.另一方面,两名学生一个接一个地参加调查并提交相同的回答是完全有效的。然而,由于调查是匿名的,我们无法区分一个学生和另一个学生,尽管考虑到调查的长度,我们并不担心同一个学生会填写两次调查。

在带有 PHP 风格的伪代码中,这是我想出的。

// application.php
start_session()
if form_posted
  validate_form
  if no_errors
    save_form
    $_SESSION['form_status'] = 'closed'
    header ('Location: confirmation.php')
  else
    //errors, so redisplay form
    $caller = 'process'
    include ('form.php')
  endif
else //form not posted
  if (!isset($_SESSION['form_status']))
    //first time displaying form
    $_SESSION['form_status'] = 'open'
    $caller = 'process'
    include ('form.php')
  elsif $_SESSION['form_status'] == 'open'
    //user refreshed form before submitting it
    unset ($_SESSION['form_status'])
    header ('Location: application.php')
  elsif $_SESSION['form_status'] == 'closed'
    //user pressed back button from confirmation_page
    header ('Location: confirmation.php')
  endif
endif

//confirmation.php
$caller = 'confirmation'
include ('form.php')

//form.php
<form>
  fields
  fields
  fields
  //only show submit button when $caller == 'process'
  if $caller == 'process'
    show_submit_button
  elseif $caller == 'confirmation'
    link ('click to fill out another form', return.php)
  endif
</form>

//return.php
session_start()
unset_session_variables
header('Location: application.php');

与发布给自己的所有脚本一样,脚本正在运行是因为表单已发布或出于其他原因。如果表单已发布,请对其进行验证、保存数据、将表单标记为“已关闭”并重定向到确认页面。

如果表单没有张贴,我们在这里的原因可能有多种。一个原因是这是表单周期的开始,第一次通过。在这种情况下,将表单标记为“打开”并显示该表单。另一个原因是用户在填写表单时刷新了表单。在这种情况下,重置表单状态并让脚本认为这是第一次运行。但是如果表单状态是“关闭”,那只能意味着用户正在盯着确认页面并按下后退按钮。在这种情况下,我们只需将用户返回到确认页面。

确认页面有一个打开新表单的链接。该链接重置会话中的值,然后重定向到主应用程序脚本,然后就像是第一次通过一样。如果用户提交表单并且在确认页面上没有采取任何操作,而是转到 Yahoo!或 YouTube,下一个尝试参加调查的用户将获得一个确认页面,显示之前用户输入的数据。这不是问题,因为下一个用户可以使用确认页面中的链接生成一个干净、新鲜的表单。

【讨论】:

    猜你喜欢
    • 2018-02-21
    • 2011-12-06
    • 2013-02-20
    • 2018-09-14
    • 1970-01-01
    • 2019-12-09
    • 2011-10-15
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多