PHP/Tutorials/Reloadsperre

Aus SELFHTML-Wiki
< PHP‎ | Tutorials
Wechseln zu: Navigation, Suche
Beim Versand von Formularen, deren Inhalt mit einer serverseitigen Technik in eine Datenbank eingetragen wird, führt ein Neuladen nach dem Abschicken der Seite zu doppelten Datensätzen. Das ist mindestens unschön. Im alten SELFHTML-Forum hieß es deshalb auch „Bitte nur einmal auf Beitrag absenden klicken, dann abwarten!“

Dieser Artikel soll Möglichkeiten vorstellen, versehentlich mehrfach gesendete Daten zu erkennen und somit doppelte Datenbankeinträge zu verhindern. Sie bietet keinen Schutz vor beispielsweise durch Bots automatisiert versendete Formulardaten. Die Möglichkeit die eingegebenen Daten zu korrigieren und das Formular danach erneut abzusenden bleibt unberührt.

Daten enthalten Primärschlüssel

Falls mit dem Formular Daten übertragen werden sollen, die es in dieser Form nur einmalig in der Datenbank geben soll, beispielsweise einen Benutzernamen, so lässt sich durch eine Datenbankabfrage erreichen, dass dieser Benutzer nicht erneut (und zwar weder versehentlich noch absichtlich) in die Datenbank eingetragen werden kann.

künstliche Primärschlüssel

Die Werte eines künstlichen Primärschlüssels werden vom Datenbanksystem automatisch generiert und dienen ausschließlich der Identifikation des Datensatzes. Es gibt keinerlei inhaltliche Verbindung zwischen dem Primärschlüssel und den anderen Attributen.

Beispiel
$id_check = $db -> prepare("SELECT `ID` FROM `Benutzer` WHERE `Benutzername` = :BName;");
$id_check -> execute(array(':BName' => $_POST['bname']));
$benutzer = $id_check -> fetch(PDO::FETCH_ASSOC);
if (isset($benutzer['ID'])) :
    array_push($fehler,'Dieser Benutzer existiert schon.');
endif;

// weitere Prüfungen wie korrektes Passwort, …

if (empty($fehler) :
    // Daten eintragen
endif;

Ein versehentlich doppelt abgesendeter Datensatz würde ohne diese Prüfung zweimal in der Datenbank erscheinen. Die beiden Datensätze würden sich lediglich anhand ihrer ID unterscheiden.

natürliche Primärschlüssel

Natürliche (oder auch sprechende) Primärschlüssel stehen mit den Daten selbst im Zusammenhang. So könnte etwa der Benutzername, die Fahrgestellnummer eines Fahrzeugs oder die SWIFT-Adresse als Primärschlüssel verwendet werden.

Beispiel
$stmt = $db -> prepare("INSERT INTO `Benutzer` (BName, Name, Vorname) VALUES (:BName, :Name, :Vorname);");
$stmt -> execute(array(':BName'   => $_POST['benutzername'], 
                       ':Name'    => $_POST['familienname'],
                       ':Vorname' => $_POST['vorname']));
$fehler = $stmt -> errorCode();

Für MySQL wird im Falle des erfolgreichen Eintragens in die Tabelle der Errorcode 00000 sein. Treten jetzt doppelte Primärschlüssel auf, so lautet der Errorcode 23000[1] und die entsprechende Fehlermeldung

Error: Can't write; duplicate key in table 'Benutzer'


Dies funktioniert natürlich auch dann, wenn zwischen den beiden Benutzer-Registrierungen eine große Zeitspanne liegt, damit ist dies mehr als eine bloße Reloadsperre.

Weiterleitung auf eine andere Seite

Wenn die Daten nicht eindeutig sein müssen, wird häufig empfohlen, auf eine Seite weiterzuleiten, die nicht das Formular enthält, sondern lediglich eine Mitteilung darüber, dass die Übertragung der Daten erfolgreich war.

Diese Form einer Reloadsperre ist sehr einfach umzusetzen und hat sich deshalb als best practice etabliert.

Beispiel
<form action="daten_verarbeiten.php" methode="post">
    <!-- Formularelemente -->
</form>
/** daten_verarbeiten.php **/
if (/* Daten erfolgreich in die Datenbank geschrieben */) :
    header('Location: https://' . $_SERVER['HTTP_HOST'] . '/erfolg.html');
    exit;
endif;

Ein Neuladen beispielsweise durch das Drücken von F5 lädt lediglich das Dokument „erfolg.html“ neu. Die Daten aus dem POST-Request werden nicht erneut übertragen. Ein Zurückgehen zum Formular selbst und ein erneutes absichtliches Absenden ist dennoch möglich und gewollt.

Findet diese Weiterleitung nicht statt, das heißt, die Erfolgsmeldung wird entweder vom Formulardokument selbst oder von dem Dokument erzeugt, welches die Daten entgegennimmt, so werden ggf. nach einigen Warnhinweisen und Nachfragen die Formulardaten erneut und unerwünschterweise versendet.

Screenshot Dokument ist nicht mehr im Browsercache verfügbar

Screenshot Sollen die Daten erneut gesendet werden?

Token mitsenden

Eine weitere Möglichkeit, das versehentliche mehrfache Absenden von Formulardaten zu verhindern, ist das Mitsenden eines Tokens, das beim Öffnen der Formularseite stets neu generiert wird. Ein Token muss eindeutig sein, PHP bietet hierfür etwa die Funktion uniqid an, die eine aus der aktuellen Systemzeit ermittelte und (sehr wahrscheinlich) eindeutige ID zurückliefert; für komplexe Anwendungen lässt sich ein Präfix hinzufügen oder die Wahrscheinlichkeit der Eindeutigkeit erhöhen, theoretisch reicht auch die Übertragung des aktuellen Zeitstempels in Millisekunden (microtime) für solche nicht sicherheitsrelevanten Übertragungen aus. Es geht ja lediglich darum, versehentlich mehrfach abgesendete Daten zu erkennen.

Das Token wird als verstecktes Formularfeld mitgesendet

Beispiel
<?php $token = uniqid(); ?>
<form method="post">
    <input type="hidden" name="token" value="<?= $token ?>">
    <!-- weitere Formularfelder, submit-Button -->
</form>

und als eindeutiger Wert in der Datenbank gespeichert.

Jetzt kann wie im ersten Abschnitt überprüft werden, ob es schon einen Eintrag mit diesem Token gibt.

Eine Token-basierte Reload-Sperre kann auch mit dem Schutz vor Cross Site Request Forgery-Lücken kombiniert werden.

Quellen

  1. dev.mysql.com: SQL-Errorcodes