再次,停止后退按钮与nonce重新提交

时间:2014-12-09 21:59:15

标签: php forms back

伙计们,我知道这个话题已经被删除了,但是在阅读了许多已回答的问题之后,我并没有接近解决方案。

问题:提交表单后,即使使用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刷新,而是后退按钮刷新。

3 个答案:

答案 0 :(得分: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">

答案 1 :(得分:1)

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

RobertSF:我正在为此做出答案,因为我必须了解在这些情况下该怎么做。

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

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

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

1)一旦数据被验证并从'表单'接受,它就会变成'read ony'。这不是一个不寻常的要求。

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

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

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

那么,我们该怎么做呢?

我们必须生成独特的“事件密钥”,这也是“形式充满数据”的关键。

我们可以使用'nonces'来做到这一点。

每个'nonce'都有一个'state',例如'newRequest','hasData'。

我将编写一个完整但最小的程序来满足要求。我们也可以“构造”它以便它可以很容易理解,所以我将使用'mvc'结构。

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

你发出的每一个表格都必须有一个独特的“随机数”,当它可能,同一个表格稍后回来时,可以检查它的状态!实际上,有许多可能的“主动”形式“在那里”,每个形式都有一个“独特的随机数”和相关的状态。

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

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

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

答案 2 :(得分:0)

我期待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');

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

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

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