Jak zapobiec wielokrotnemu przesyłaniu formularza po stronie klienta?

96

Czasami, gdy odpowiedź jest powolna, można wielokrotnie kliknąć przycisk przesyłania.

Jak temu zapobiec?

o mój Boże
źródło
4
Mam nadzieję, że powodem, dla którego umieściłeś słowo „po stronie klienta” w tytule swojego pytania, jest to, że wiesz, że każde rozwiązanie na kliencie pozwalające uniknąć podwójnych zgłoszeń jest w 100% niezabezpieczone i zawsze powinieneś przeprowadzać weryfikację po stronie serwera.
Paolo Bergantino
51
Paolo: Tak, jeśli interesuje Cię pełne bezpieczeństwo danych, powinno to być zrobione po stronie serwera. Ale czasami po prostu chcesz uniemożliwić babci dwukrotne kliknięcie przycisku przesyłania, gdy wymaga to tylko jednego kliknięcia. W takich przypadkach javascript jest w porządku.
Simon East,
1
Zrobienie tego tylko po stronie serwera nie jest również w 100% bezpieczne, ponieważ zakończenie pierwszego żądania może zająć wystarczająco dużo czasu, aby drugie wiedziało, że nie powinno być kontynuowane.
igorsantos 07
Czy wzorzec pliku cookie Double-Submit, aby zapobiec atakom CSRF, nie zapobiega również przesyłaniu wielu formularzy?
Martin Thoma,

Odpowiedzi:

99

Użyj dyskretnego javascript, aby wyłączyć zdarzenie przesyłania w formularzu po jego już przesłaniu. Oto przykład użycia jQuery.

EDYCJA: Naprawiono problem z przesyłaniem formularza bez klikania przycisku przesyłania. Dzięki, ichibanie.

$("body").on("submit", "form", function() {
    $(this).submit(function() {
        return false;
    });
    return true;
});
Starx
źródło
4
Co się stanie, jeśli formularz zostanie przesłany przez naciśnięcie klawisza Enter w polu tekstowym?
ichiban
2
Czy w ogóle potrzebujesz return true? Myślę, że pierwsze zgłoszenie powinno działać bez tego.
Simon East,
Uważam, że return truejest to domniemane, jeśli zostanie pominięte.
Derek
15
Wypróbowałem to rozwiązanie z dyskretną walidacją i MVC. JS jest wywoływany jako pierwszy, co zawsze zwraca false, a następnie wywoływana jest walidacja. Więc jeśli mój formularz ma jakiś błąd walidacji, formularz nigdy nie zostanie przesłany !!
bjan
6
działa naprawdę świetnie. Chciałbym również wyłączyć przycisk przesyłania i zastąpić tekst przycisku przesyłania, aby również wyświetlał wskazówkę dla użytkownika. $(this).find("input[type='submit']").attr('disabled', 'disabled').val('submiting'); return true; });
Cam Song
42

Wypróbowałem rozwiązanie Vanstee wraz z dyskretną walidacją asp mvc 3, a jeśli walidacja klienta się nie powiedzie, kod jest nadal uruchamiany, a przesyłanie formularzy jest wyłączone na dobre. Nie mogę przesłać ponownie po poprawieniu pól. (patrz komentarz bjan)

Więc zmodyfikowałem skrypt vanstee w ten sposób:

$("form").submit(function () {
    if ($(this).valid()) {
        $(this).submit(function () {
            return false;
        });
        return true;
    }
    else {
        return false;
    }
});
m irina
źródło
Jakieś inne efekty uboczne, na które warto zwrócić uwagę, czy też sprawdziło się to do zastosowania wszędzie?
Ciaran Gallagher
24

Kontrolę przesyłania formularzy po stronie klienta można osiągnąć całkiem elegancko, jeśli program obsługi onsubmit ukryje przycisk przesyłania i zastąpi go animacją ładowania. W ten sposób użytkownik otrzymuje natychmiastową wizualną informację zwrotną w tym samym miejscu, w którym miało miejsce jego działanie (kliknięcie). Jednocześnie uniemożliwiasz przesłanie formularza innym razem.

Jeśli przesyłasz formularz przez XHR, pamiętaj, że musisz również zająć się błędami przesyłania, na przykład przekroczeniem limitu czasu. Musiałbyś ponownie wyświetlić przycisk przesyłania, ponieważ użytkownik musi ponownie przesłać formularz.

Z drugiej strony llimllib przedstawia bardzo ważny punkt. Cała weryfikacja formularza musi odbywać się po stronie serwera. Obejmuje to wielokrotne sprawdzanie przedłożenia. Nigdy nie ufaj klientowi! Dzieje się tak nie tylko w przypadku wyłączenia javascript. Należy pamiętać, że cały kod po stronie klienta można modyfikować. Trudno to sobie wyobrazić, ale html / javascript komunikujący się z serwerem niekoniecznie musi być napisanym przez Ciebie html / javascript.

Jak sugeruje llimllib, wygeneruj formularz z identyfikatorem, który jest unikalny dla tego formularza i umieść go w ukrytym polu wejściowym. Przechowuj ten identyfikator. Podczas otrzymywania danych formularza przetwarzaj je tylko wtedy, gdy identyfikator jest zgodny. (Również łączenie identyfikatora z sesją użytkownika i dopasowywanie go również dla dodatkowego bezpieczeństwa). Po przetworzeniu danych usuń identyfikator.

Oczywiście od czasu do czasu musiałbyś wyczyścić identyfikatory, dla których nigdy nie przesłano żadnych danych formularza. Ale najprawdopodobniej Twoja witryna już wykorzystuje jakiś mechanizm „zbierania śmieci”.

Obrabować
źródło
15
<form onsubmit="if(submitted) return false; submitted = true; return true">
chaos
źródło
6
Użycie <form onsubmit="return (typeof submitted == 'undefined') ? (submitted = true) : !submitted">lub zapisu var submitted = false;w odpowiednim momencie swojego skryptu, aby uniknąć następujący błąd: Uncaught ReferenceError: submitted is not defined.
Tamás Bolvári
15

Oto prosty sposób, aby to zrobić:

<form onsubmit="return checkBeforeSubmit()">
  some input:<input type="text">
  <input type="submit" value="submit" />
</form>

<script type="text/javascript">
  var wasSubmitted = false;    
    function checkBeforeSubmit(){
      if(!wasSubmitted) {
        wasSubmitted = true;
        return wasSubmitted;
      }
      return false;
    }    
</script>
ichiban
źródło
z dyskretną walidacją jQuery, skrypt blokujący jest wywoływany jako pierwszy, a następnie walidacja, co skutkuje brakiem przesyłania, jeśli występują błędy walidacji
bjan
8

Wyłącz przycisk przesyłania wkrótce po kliknięciu. Upewnij się, że poprawnie obsługujesz walidacje. Zachowaj również stronę pośrednią dla wszystkich operacji przetwarzania lub operacji DB, a następnie przekieruj do następnej strony. Daje to pewność, że odświeżenie drugiej strony nie spowoduje kolejnego przetwarzania.

Shoban
źródło
Tak, świetna wskazówka Shoban, zgadzam się. Powinno byćform page ---submits to---> form_submission_script.php ---after saving, redirects to---> form_thankyou.html
Simon East,
6

Hasz bieżący czas, uczyń go ukrytym wejściem w formularzu. Po stronie serwera sprawdź hash każdego przesłanego formularza; jeśli już otrzymałeś ten hash, masz powtórne przesłanie.

edycja: poleganie na javascript nie jest dobrym pomysłem, więc wszyscy możecie dalej głosować za tymi pomysłami, ale niektórzy użytkownicy nie będą mieli włączonej opcji. Prawidłowa odpowiedź brzmi: nie ufaj wejściom użytkownika po stronie serwera.

llimllib
źródło
Czy to nie wygląda na „za dużo”? ;-)
Shoban
1
Wygląda na to, że uniemożliwiłoby to użytkownikowi przesyłanie formularza częściej niż raz na sekundę. Jeśli wyślę formularz o 2009-05-29 12:13:37, serwer nie pozwoli mi przesłać kolejnego formularza z tym samym hashem, ale jeśli kliknąłem Prześlij o 2009-05-29 12:13:38, to przejdzie. Ponadto twoja technika uniemożliwiłaby 2 różnym użytkownikom jednoczesne przesłanie dwóch różnych formularzy: jeden z nich zostałby wyrzucony, nawet jeśli byłby to prawdopodobnie pierwszy wniosek od niego.
Sarah Vessels
2
@moneypenny Twój pierwszy sprzeciw jest fałszywy, hash dotyczyłby czasu wysłania formularza do użytkownika, a nie czasu przesłania. Jeśli naprawdę martwisz się o drugą, masz wiele opcji. Dodaj ich identyfikator sesji do ciągu, który haszujesz i / lub sprawdź wszystkie pola formularza pod kątem dokładnej identyczności; dwóch użytkowników prawdopodobnie nie przesłało dokładnie tego samego formularza.
llimllib
@Shoban Przepraszam, nie rozumiem tego, co mówisz
llimllib
2
„Nadmiar” jest w oku patrzącego. Jeśli naprawdę chcesz zapobiec zduplikowanym przesyłaniu formularzy, przynajmniej część rozwiązania musi być po stronie serwera. „Nie możesz ufać użytkownikom”.
Ben Blank
5

Możesz również wyświetlić pasek postępu lub pokrętło, aby wskazać, że formularz jest przetwarzany.

Tony Borf
źródło
5

Za pomocą JQuery możesz:

$('input:submit').click( function() { this.disabled = true } );

&

   $('input:submit').keypress( function(e) {
     if (e.which == 13) {
        this.disabled = true 
     } 
    }
   );
Boris Guéry
źródło
1
Co się stanie, jeśli formularz zostanie przesłany przez naciśnięcie klawisza ENTER w polu tekstowym po wyłączeniu przycisku?
ichiban
Wyłączenie tego przycisku uniemożliwi użytkownikowi naciśnięcie klawisza ENTER. Ale, racja, musi przytrzymać naciśnięcie klawisza na wejściu przesyłania
Boris Guéry
Zapobiega to wysyłaniu przycisku. Stąd wyłączenie weryfikacji po stronie serwera.
Marko Aleksić
@Marko, zgadza się, ale to jest OP, o którą proszono, w każdym razie użycie tokena uniemożliwiłoby dwukrotne przesłanie formularza.
Boris Guéry
1
Kiedy próbowałem tego rodzaju techniki, stwierdziłem, że formularz w ogóle się nie przesłał (przynajmniej nie w Chrome 14), prawdopodobnie dlatego, że wyłączasz przycisk, zanim przeglądarka rozpocznie przesyłanie formularza.
Simon East,
4

Wiem, że oznaczyłeś swoje pytanie tagiem „javascript”, ale oto rozwiązanie, które w ogóle nie zależy od javascript:

Jest to wzorzec aplikacji internetowej o nazwie PRG , a oto dobry artykuł, który go opisuje

Pablo Fernandez
źródło
4

Aby zapobiec wielokrotnemu przesyłaniu, wystarczy:

var Workin = false;

$('form').submit(function()
{
   if(Workin) return false;
   Workin =true;

   // codes here.
   // Once you finish turn the Workin variable into false 
   // to enable the submit event again
   Workin = false;

});
Alfa
źródło
Problem z twoją odpowiedzią polega na tym, że między momentem kliknięcia przez użytkownika a czasem jest ułamek sekundy Workin =true;. Podwójne przesyłanie jest nadal możliwe, ale prawdopodobnie jest ich mniej.
sent1nel
4

Najprostsza odpowiedź na to pytanie: „Czasami, gdy odpowiedź jest powolna, można kilka razy kliknąć przycisk przesyłania. Jak temu zapobiec?”

Po prostu wyłącz przycisk przesyłania formularza, jak w poniższym kodzie.

<form ... onsubmit="buttonName.disabled=true; return true;">
  <input type="submit" name="buttonName" value="Submit">
</form>

Spowoduje to wyłączenie przycisku przesyłania po pierwszym kliknięciu w celu przesłania. Również jeśli masz jakieś reguły walidacji, to będzie działać dobrze. Mam nadzieję, że to pomoże.

Imran Khan
źródło
Myślę, że czegoś brakuje, prześlij swój kod.
Imran Khan
3

Po stronie klienta należy wyłączyć przycisk przesyłania po przesłaniu formularza z kodem javascript, tak jak w przypadku metody dostarczonej przez @vanstee i @chaos.

Ale istnieje problem z opóźnieniem sieci lub sytuacją z wyłączoną obsługą javascript, w której nie powinieneś polegać na JS, aby temu zapobiec.

Tak więc po stronie serwera należy sprawdzić powtórne przesłanie od tych samych klientów i pominąć powtórzone, które wydaje się fałszywą próbą użytkownika.

steveyang
źródło
Wiem, że odpowiedziano na to dawno temu, ale czy ktoś może wyjaśnić, w jaki sposób opóźnienie w sieci może uniemożliwić wyłączenie? Mam do czynienia z tym problemem w tej chwili i jestem bardzo zdezorientowany, w jaki sposób formularz został przesłany dwukrotnie, mimo że wyłączam przycisk. Nigdy nawet nie myślałem o wyłączeniu JavaScript. Nie sądzę, żeby to był problem w moim przypadku, ale mimo wszystko jest to interesujący wgląd.
davidatthepark
Zastanawiałem się również nad ograniczeniem obsługi kliknięcia.
davidatthepark
Jak sprawdzisz po stronie serwera, jeśli przesyłanie pochodzi od tego samego klienta? Według adresu IP użytkownika? Jeśli tak, gdzie można „zapisać” adres IP po stronie serwera z wcześniej przesłanego wpisu? (Używam węzła, jeśli ten kontekst jest wymagany)
user1063287
3

Możesz wypróbować wtyczkę safeform jquery.

$('#example').safeform({
    timeout: 5000, // disable form on 5 sec. after submit
    submit: function(event) {
        // put here validation and ajax stuff...

        // no need to wait for timeout, re-enable the form ASAP
        $(this).safeform('complete');
        return false;
    }
})
Max Kamenkov
źródło
1

Najprostsze i eleganckie rozwiązanie dla mnie:

function checkForm(form) // Submit button clicked
{
    form.myButton.disabled = true;
    form.myButton.value = "Please wait...";
    return true;
}

<form method="POST" action="..." onsubmit="return checkForm(this);">
    ...
    <input type="submit" name="myButton" value="Submit">
</form>

Link po więcej ...

Solis
źródło
1

Użyj tego kodu w swoim formularzu. Obsłuży wiele kliknięć.

<script type="text/javascript">
    $(document).ready(function() {
        $("form").submit(function() {
            $(this).submit(function() {
                return false;
            });
            return true;
        });     
    }); 
</script>

to na pewno zadziała.

Bacha
źródło
1

Umożliwia to przesyłanie co 2 sekundy. W przypadku walidacji z przodu.

$(document).ready(function() {
    $('form[debounce]').submit(function(e) {
        const submiting = !!$(this).data('submiting');

        if(!submiting) {
            $(this).data('submiting', true);

            setTimeout(() => {
                $(this).data('submiting', false);
            }, 2000);

            return true;
        }

        e.preventDefault();
        return false;
    });
})
Aurel
źródło
0

Najlepszym sposobem na uniknięcie przesyłania wielokrotnych jest po prostu przekazanie identyfikatora przycisku w metodzie.

    function DisableButton() {
        document.getElementById("btnPostJob").disabled = true;
    }
    window.onbeforeunload = DisableButton; 
user2427182
źródło
0

Aby to zrobić za pomocą javascript jest trochę łatwe. Poniżej znajduje się kod, który zapewni pożądaną funkcjonalność:

$('#disable').on('click', function(){
    $('#disable').attr("disabled", true);
  });
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<button id="disable">Disable Me!</button>

Rish
źródło
0

Najprostszym rozwiązaniem jest wyłączenie przycisku po kliknięciu i włączenie go po zakończeniu operacji. Aby sprawdzić podobne rozwiązanie na jsfiddle:

[click here][1]

W tej odpowiedzi możesz znaleźć inne rozwiązanie .

Kalyani
źródło
1
Chociaż link jest świetny, podaj kontekst dla swoich linków , ponieważ odpowiedź powinna faktycznie zawierać odpowiedź. Należy pamiętać, że fakt, że jest zaledwie linkiem do zewnętrznej witryny, może wskazywać na Dlaczego i w jaki sposób niektóre odpowiedzi są usuwane? .
Sty
0

U mnie to działa bardzo dobrze. Podaje farmę i wyłącza przycisk, a po 2 sekundach aktywuje przycisk.

<button id="submit" type="submit" onclick="submitLimit()">Yes</button>

function submitLimit() {
var btn = document.getElementById('submit')
setTimeout(function() {
    btn.setAttribute('disabled', 'disabled');
}, 1);

setTimeout(function() {
    btn.removeAttribute('disabled');
}, 2000);}

W ECMA6 Syntex

function submitLimit() {
submitBtn = document.getElementById('submit');

setTimeout(() => { submitBtn.setAttribute('disabled', 'disabled') }, 1);

setTimeout(() => { submitBtn.removeAttribute('disabled') }, 4000);}
Awais Jameel
źródło
0

Wystarczy dodać do możliwych odpowiedzi bez pomijania sprawdzania poprawności danych wejściowych przeglądarki

$( document ).ready(function() {
    $('.btn-submit').on('click', function() {
        if(this.form.checkValidity()) {
            $(this).attr("disabled", "disabled");
            $(this).val("Submitting...");
            this.form.submit();
        }
    });
});
Roi
źródło
0

Alternatywą dla tego, co zaproponowano wcześniej, jest:

jQuery('form').submit(function(){
     $(this).find(':submit').attr( 'disabled','disabled' );
     //the rest of your code
});
Kisded Szabi - CodeRevolution
źródło