Kilkukrotne wysyłanie formularza HTML/PHP

0

Witam!
Mam standardowy formularz w HTML (<form> </form>) i moje pytanie brzmi: jak można zabezpieczyć go przed kilkukrotnym wysyłaniem? Gdy kliknie się szybko kilka razy na przycisk Wyślij to formularz wysyła się kilka razy i właśnie chciałem temu zapobiec tylko nie bardzo wiem jak. Cała strona jest w PHP, więc może też być coś z użyciem PHP.
Z góry dzięki wielkie za pomoc!

0

w onsubmit formularza zablokuj przycisk wysyłania, albo przy pierwszym wysłaniu ustaw jakąś zmienną globalną na true, a przy kolejnych razach sprawdzaj, czy ma już taką wartość i zwracaj false.

2

Proste, ale na swój sposób niepewne (tj. odświeżanie przy pomocy F5 ponownie wyśle dane) metody to zablokowanie przycisku poprzez javascript, najprościej:

<input type="submit" onclick='this.value="Czekaj"; this.disabled="disabled";' />

Druga opcja, dłuższa w implementacji, ale skuteczniejsza (najlepiej połączyć obie metody, bo metody server-side są skuteczne, a metody javascript - wygodne):

Musisz mieć obsługę sesji (session_start() na początku każdego skryptu) i potem coś takiego:

function form_new_id() {
  if (!isset($_SESSION['forms']) || is_array($_SESSION['forms'])) { $_SESSION['forms']=array(); }
  $r = md5(microtime());
  $_SESSION['forms'][$r] = true;
  return $r;
}

function form_use_id($id) {
  if (!isset($_SESSION['forms'][$id])) { return null; }
  elseif ($_SESSION['forms'][$id]) {
    $_SESSION['forms'][$id] = false;
    return true;
  }
  else { return false; }
}

Przy wyświetlaniu formy:

<form action=".." method="post">
  <input type="hidden" name="__form_id" value="<?php echo form_new_id(); ?>" />
  ..
</form>

I przy przetwarzaniu formy:

$id = $_POST['__form_id']; // albo $_GET
$test = form_use_id($id);
if ($test===null) { // uwaga na ===
  echo "Sesja wygasła, bądź nie masz włączonej obsługi ciasteczek";
}
elseif ($test===false) {
  echo "Formularz był już wysłany";
}
else {
  // akcje
  echo "Sukces, jakaś akcja została wykonana";
}

Pisane z palca, nie testowane, powinno zadziałać. Można to ładniej napisać, ale chciałem, żeby było zrozumiałe.

0

Ok dzięki bardzo pomogłeś :)
Ze zmienną myślałem no ale właśnie jak się odświeży stronę to już nic to nie da...
No więc na pewno skorzystam z zaproponowanych sposobów.

0

po odebraniu formularza jeszcze przekieruj przeglądarkę na inny adres (header('Location: /zapisalemDane/dziekuje')), wtedy użytkownik praktycznie nie będzie mieć okazji podwójnego wysłania formularza. wtedy w większości przypadków wystarczy zabezpieczenie js - tak masz na tym forum.

0

Przekierowanie jest głupie, bo głupio się podaje komunikaty o sukcesie/porażce danej operacji. Musisz przekierować użytkownika do właśnie jakiegoś /zapisalemDane/dziekuje, a pod ten link potem można sobie przejść z historii, albo wysłać komuś przez komunikator i potem ktoś czyta: "Operacja powiodła się" wzięte znikąd. A jeszcze lepiej jak chcesz wypisać coś typu: "Dodano konto o nazwie jan_kowalski" i w linku mieć /zapisalemDane/dziekuje?nick=jan_kowalski - wtedy już w ogóle tragedia.

1

@dzek69:

  1. Po co Ci te nadmiarowe dane w URLu jeśli masz sesję? Jeśli nie masz sesji to równie dobrze można wrzucić token.
  2. Przekierowanie z POST na GET po wysyłaniu formularzy to podstawa, w historii tego przekierowania nie powinno być widać.
0
  1. No a jak rozwiążesz problem jednoczesnego otwarcia tego samego formularza w 3 kartach przeglądarki? Zapisywać dane do pokazania na przekierowanym adresie w sesji? - nie brzmi bezpiecznie, szczególnie w przypadku gdy net zamula i/lub przetwarzanie trwa dłuższy czas - jest czas na potwierdzenie submita kilka razy z kilku kart, a potem losowe komunikaty. Trzeba token jakiś przesyłać (a po przekierowaniu na GET zostaje tylko opcja w URL, co jest brzydkie, no i rodzi ten problem przesłania komuś linka dalej), więc lepiej to już obsłużyć tym co podałem wyżej - i tak jest token, a obejdzie się bez przekierowania.
  2. Że niby którego adresu nie będzie w historii? Tego ostatniego, docelowego? Nie sprawdzałem, ale nie widzę powodów, dla których przeglądarka miałaby nie zapisywać w historii adresów po przekierowaniu..
1

dzek, odkrywasz koło na nowo. tak się robi, bo tak jest najsensowniej i najefektywniej: po wysłaniu posta następuje redirect na get i nie ma, że to tamto.
żadna normalna przeglądarka (normalna w sensie w miarę standardowo skonfigurowane IE, FF, CH, Opera oraz Safari) nie zapisuje w historii strony, z której przyszło 302 (czy też inny redirect). oczywiście, zapisanie w historii dokładnie każdej odwiedzanej strony jak najbardziej możliwe i to razem z wysłanymi danymi, ale powtarzam, żadna normalna przeglądarka tego nie robi. w historii ląduje ostatnia strona z serii przekierowań, łatwo to możesz zrobić wysyłając odpowiedź na tego posta, a potem cofając się o jedną stronę - przeglądarka nie poprosi Cię o pozwolenie na ponowne wysłanie formularza. to właśnie efekt 302.
można założyć, że przeglądarka nie wrzuci do historii strony, z której przyszedł redirect, i że nie zapamięta danych, które tam wysłała, ale nie można być stuprocentowo tego pewnym i tu możesz mieć rację. tylko co z tego? komunikacja klient-serwer to w dużym stopniu wymiana grzeczności: keep-alive, cache-control, redirecty, historia, ja cię poproszę o coś, a ty być może tak zrobisz, ale nie masz obowiązku, stąd zasada ograniczonego zaufania do tego, co się dzieje po stronie klienta z punktu widzenia kodu na serwerze i vice versa. jednak mimo tylu założeń, próśb i grzeczności wszystko po prostu działa.

tak samo jak Tobie wydaje się, że ktoś będzie wypełniał ten sam formularz w trzech zakładkach mi wydaje się, że odsetek ludzi tak robiących jest pomijalnie mały. oczywiście, w miejscach gdzie coś takiego może zepsuć jakiś ważny mechanizm działania strony należy to zablokować, jednak sesja niekoniecznie jest najlepszym miejscem na to, chyba, że poparta jest mechanizmem logowania. bez tego złośliwy ludek po prostu wyłączy ciastka i co? jaka sesja? na takie rzeczy i baza danych bez systemu logowania się nie poradzi. poza tym Twój kod ma dwie dziury:

  • nie bierzesz pod uwagę współbieżności. przy pewnej dozie szczęścia na tysiąc wysłanych jednocześnie requestów post dwa wykonają się równolegle;
  • lekceważąc historię sam doprowadzasz do sytuacji, kiedy w przeglądarce zostaje adres strony wraz z danymi, które pod ten adres zostały wysłane. choć tu nie ma to znaczenia, w najbardziej ogólnym przypadku to spora dziura w bezpieczeństwie.
    Twoje rozwiązanie będzie ok, ale tylko razem z redirectem (i js, żeby oszczędzić zbędnych requestów).

[edit]
"No a jak rozwiążesz problem jednoczesnego otwarcia tego samego formularza w 3 kartach przeglądarki?"
jak chciałbyś to zrealizować? potrójne kliknięcie kółkiem myszki? przecież to nie wyśle formularza. czym grozi trzykrotne otworzenie (nie wysłanie zawartości - przeglądarki i strony www nie działają w ten sposób, chyba że jakiś szalony webdev ustawi target - co jest łatwe do zablokowania z js) formularza w trzech zakładkach.

amen.

1 użytkowników online, w tym zalogowanych: 0, gości: 1