Nie powiodło się sprawdzenie Django CSRF przy żądaniu Ajax POST

180

Mógłbym skorzystać z pomocy w przestrzeganiu mechanizmu ochrony CSRF Django za pośrednictwem mojego postu AJAX. Postępowałem zgodnie ze wskazówkami tutaj:

http://docs.djangoproject.com/en/dev/ref/contrib/csrf/

Skopiowałem dokładnie przykładowy kod AJAX, który mają na tej stronie:

http://docs.djangoproject.com/en/dev/ref/contrib/csrf/#ajax

Umieszczam alert drukujący zawartość getCookie('csrftoken')przed xhr.setRequestHeaderpołączeniem i rzeczywiście jest on wypełniony niektórymi danymi. Nie jestem pewien, jak sprawdzić, czy token jest poprawny, ale zachęcam mnie do znalezienia i wysłania czegoś.

Ale Django nadal odrzuca mój post AJAX.

Oto mój JavaScript:

$.post("/memorize/", data, function (result) {
    if (result != "failure") {
        get_random_card();
    }
    else {
        alert("Failed to save card data.");
    }
});

Oto błąd, który widzę w Django:

[23 / lut / 2011 22:08:29] „POST / memorize / HTTP / 1.1” 403 2332

Jestem pewien, że coś mi umknęło, a może to proste, ale nie wiem, co to jest. Szukałem w SO i widziałem informacje o wyłączaniu sprawdzania CSRF dla mojego widoku za pomocą csrf_exemptdekoratora, ale uważam to za nieatrakcyjne. Wypróbowałem to i działa, ale wolę, aby mój test POST działał w sposób, w jaki zaprojektowano Django, jeśli to możliwe.

Na wszelki wypadek pomocne jest to, co robi mój pogląd:

def myview(request):

    profile = request.user.profile

    if request.method == 'POST':
        """
        Process the post...
        """
        return HttpResponseRedirect('/memorize/')
    else: # request.method == 'GET'

        ajax = request.GET.has_key('ajax')

        """
        Some irrelevent code...
        """

        if ajax:
            response = HttpResponse()
            profile.get_stack_json(response)
            return response
        else:
            """
            Get data to send along with the content of the page.
            """

        return render_to_response('memorize/memorize.html',
                """ My data """
                context_instance=RequestContext(request))

Dziękuję za odpowiedzi!

firebush
źródło
1
Jakiej wersji django używasz?
zsquare
Czy dodałeś do odpowiednich klas oprogramowania pośredniego CSRF i umieściłeś je we właściwej kolejności?
darren
Jakub odpowiedział na moje pytanie poniżej, ale na wszelki wypadek jest użyteczne dla innych osób: @zsquare: wersja 1.2.3. @mongoose_za: Tak, są dodawane i we właściwej kolejności.
firebush

Odpowiedzi:

181

Prawdziwe rozwiązanie

Ok, udało mi się wyśledzić problem. Jest w kodzie JavaScript (jak zasugerowałem poniżej).

Potrzebujesz tego:

$.ajaxSetup({ 
     beforeSend: function(xhr, settings) {
         function getCookie(name) {
             var cookieValue = null;
             if (document.cookie && document.cookie != '') {
                 var cookies = document.cookie.split(';');
                 for (var i = 0; i < cookies.length; i++) {
                     var cookie = jQuery.trim(cookies[i]);
                     // Does this cookie string begin with the name we want?
                     if (cookie.substring(0, name.length + 1) == (name + '=')) {
                         cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
                         break;
                     }
                 }
             }
             return cookieValue;
         }
         if (!(/^http:.*/.test(settings.url) || /^https:.*/.test(settings.url))) {
             // Only send the token to relative URLs i.e. locally.
             xhr.setRequestHeader("X-CSRFToken", getCookie('csrftoken'));
         }
     } 
});

zamiast kodu opublikowanego w oficjalnych dokumentach: https://docs.djangoproject.com/en/2.2/ref/csrf/

Działający kod pochodzi z tego wpisu Django: http://www.djangoproject.com/weblog/2011/feb/08/security/

Tak więc ogólne rozwiązanie to: „użyj modułu obsługi ajaxSetup zamiast modułu obsługi ajaxSend”. Nie wiem, dlaczego to działa. Ale to działa dla mnie :)

Poprzedni post (bez odpowiedzi)

Właściwie mam ten sam problem.

Występuje po aktualizacji do Django 1.2.5 - w Django 1.2.4 nie wystąpiły błędy z żądaniami AJAX POST (AJAX nie był w żaden sposób chroniony, ale działał dobrze).

Podobnie jak OP, wypróbowałem fragment kodu JavaScript opublikowany w dokumentacji Django. Korzystam z jQuery 1.5. Używam również oprogramowania pośredniego „django.middleware.csrf.CsrfViewMiddleware”.

Próbowałem postępować zgodnie z kodem oprogramowania pośredniego i wiem, że to się nie udaje:

request_csrf_token = request.META.get('HTTP_X_CSRFTOKEN', '')

i wtedy

if request_csrf_token != csrf_token:
    return self._reject(request, REASON_BAD_TOKEN)

to „if” jest prawdziwe, ponieważ „request_csrf_token” jest pusty.

Zasadniczo oznacza to, że nagłówek NIE jest ustawiony. Czy jest coś nie tak z tą linią JS:

xhr.setRequestHeader("X-CSRFToken", getCookie('csrftoken'));

?

Mam nadzieję, że podane szczegóły pomogą nam rozwiązać problem :)

Jakub Gocławski
źródło
To zadziałało! Włączyłem funkcję .ajaxSetup, tak jak ją wkleiłeś powyżej, i teraz mogę pisać bez błędu 403. Dziękujemy za udostępnienie rozwiązania, Jakub. Dobre znalezisko. :)
firebush
Użycie ajaxSetupzamiast ajaxSendbiegania jest sprzeczne z dokumentami jQuery: api.jquery.com/jQuery.ajaxSetup
Mark Lavin
używając 1.3, oficjalna pozycja dokumentacji django działała dla mnie.
monkut
1
Próbowałem, ale wydaje się, że to nie działa, używam jQuery v1.7.2, moje pytanie to stackoverflow.com/questions/11812694/…
daydreamer
Muszę dodać adnotację @ensure_csrf_cookie do mojej funkcji przeglądania, aby wymusić ustawienie pliku cookie csrf, gdy strona jest żądana z urządzeń mobilnych.
Kane
172

Jeśli używasz tej $.ajaxfunkcji, możesz po prostu dodać csrftoken do treści danych:

$.ajax({
    data: {
        somedata: 'somedata',
        moredata: 'moredata',
        csrfmiddlewaretoken: '{{ csrf_token }}'
    },
Bryan
źródło
2
kiedy używam zaznaczonej odpowiedzi, działa to dla mnie, ale jeśli użyję twojego rozwiązania tutaj, to nie działa. Ale twoje rozwiązanie powinno działać, nie rozumiem, dlaczego tak nie jest. Czy jest coś jeszcze do zrobienia w Django 1.4?
Houman
1
Dzięki! tak prosty. Nadal działa na django 1.8 i jquery 2.1.3
Alejandro Veintimilla
19
To rozwiązanie wymaga osadzenia javascript w szablonie, prawda?
Mox
15
@Mox: Umieść to w html, ale nad plikiem Js, gdzie jest funkcja ajax <script type="text/javascript"> window.CSRF_TOKEN = "{{ csrf_token }}"; </script>
Oto
Dzięki! Tak prosty i elegancki. Działa dla mnie z Django 1.8. Dodałem csrfmiddlewaretoken: '{{ csrf_token }}'do mojego datasłownika w trakcie $.postrozmowy.
Pavel Yudaev,
75

Dodaj ten wiersz do kodu jQuery:

$.ajaxSetup({
  data: {csrfmiddlewaretoken: '{{ csrf_token }}' },
});

i zrobione.

Kambiz
źródło
Próbowałem tego, ale mój formularz ma przesyłanie plików. Mój backend to django i nadal pojawia się błąd 400CSRF Failed: CSRF token missing or incorrect.
Hussain,
16

Problem polega na tym, że django oczekuje, że wartość z pliku cookie zostanie zwrócona jako część danych formularza. Kod z poprzedniej odpowiedzi pobiera javascript w celu wyszukania wartości pliku cookie i umieszczenia go w danych formularza. Jest to cudowny sposób na zrobienie tego z technicznego punktu widzenia, ale wygląda na trochę gadatliwego.

W przeszłości robiłem to po prostu, pobierając javascript do umieszczenia wartości tokena w danych postu.

Jeśli użyjesz {% csrf_token%} w swoim szablonie, otrzymasz wyemitowane ukryte pole formularza, które przenosi wartość. Ale jeśli użyjesz {{csrf_token}}, dostaniesz samą wartość tokena, dzięki czemu będziesz mógł użyć tego w javascript w ten sposób ....

csrf_token = "{{ csrf_token }}";

Następnie możesz dołączyć to, z wymaganą nazwą klucza w haszu, a następnie przesłać jako dane do wywołania ajax.

fatgeekuk
źródło
@aehlke Możesz mieć pliki statyczne. W kodzie źródłowym możesz zobaczyć ładny przykład, w którym rejestrujesz zmienne django w windowobiekcie, dzięki czemu są one później dostępne. Nawet w plikach statycznych.
KitKat
3
@ KitKat rzeczywiście :) Przepraszam za mój starożytny, ignorancki komentarz tutaj. Słuszna uwaga.
aehlke,
ponownie pliki statyczne. Nie stanowi problemu, jeśli nie przeszkadza ci odrobina js twojego HTML. Po prostu umieściłem {{csrf_token}} w głównym szablonie HTML, niedaleko od inkantacji RequJJ. działał jak urok.
JL Peyret
14

{% csrf_token %}Umieścić w html szablony wnętrze<form></form>

tłumaczy się na coś takiego:

<input type='hidden' name='csrfmiddlewaretoken' value='Sdgrw2HfynbFgPcZ5sjaoAI5zsMZ4wZR' />

więc dlaczego nie po prostu grep to w JS w ten sposób:

token = $("#change_password-form").find('input[name=csrfmiddlewaretoken]').val()

a następnie przekazać go np. wykonując test POST, np .:

$.post( "/panel/change_password/", {foo: bar, csrfmiddlewaretoken: token}, function(data){
    console.log(data);
});
andilabs
źródło
11

Odpowiedź niekwotowa:

var csrfcookie = function() {
    var cookieValue = null,
        name = 'csrftoken';
    if (document.cookie && document.cookie !== '') {
        var cookies = document.cookie.split(';');
        for (var i = 0; i < cookies.length; i++) {
            var cookie = cookies[i].trim();
            if (cookie.substring(0, name.length + 1) == (name + '=')) {
                cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
                break;
            }
        }
    }
    return cookieValue;
};

stosowanie:

var request = new XMLHttpRequest();
request.open('POST', url, true);
request.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded; charset=UTF-8');
request.setRequestHeader('X-CSRFToken', csrfcookie());
request.onload = callback;
request.send(data);

źródło
8

Jeśli twój formularz poprawnie publikuje w Django bez JS, powinieneś być w stanie stopniowo ulepszać go za pomocą ajax bez żadnego hakowania lub niechlujnego przekazywania tokena csrf. Wystarczy serializować cały formularz, a to automatycznie pobierze wszystkie pola formularza, w tym ukryte pole csrf:

$('#myForm').submit(function(){
    var action = $(this).attr('action');
    var that = $(this);
    $.ajax({
        url: action,
        type: 'POST',
        data: that.serialize()
        ,success: function(data){
            console.log('Success!');
        }
    });
    return false;
});

Przetestowałem to z Django 1.3+ i jQuery 1.5+. Oczywiście będzie to działać dla dowolnej formy HTML, nie tylko dla aplikacji Django.

GivP
źródło
5

Używaj Firefoksa z Firebug. Otwórz kartę „Konsola” podczas uruchamiania żądania ajax. ZDEBUG=True ciebie dostać nicejskiego django strony błędu jako odpowiedź i można nawet zobaczyć renderowany HTML w odpowiedzi ajax w zakładce konsoli.

Wtedy dowiesz się, jaki jest błąd.

jammon
źródło
5

Przyjęta odpowiedź to najprawdopodobniej czerwony śledź. Różnica między Django 1.2.4 i 1.2.5 była wymagana dla tokena CSRF dla żądań AJAX.

Natknąłem się na ten problem na Django 1.3 i był on spowodowany nie ustawieniem pliku cookie CSRF . Django nie ustawi ciasteczka, chyba że będzie to konieczne. Tak więc strona wyłącznie lub w dużej mierze działająca na Django 1.2.4 potencjalnie nigdy nie wysłałaby tokena do klienta, a wtedy aktualizacja wymagająca tokena spowodowałaby błędy 403.

Idealna poprawka znajduje się tutaj: http://docs.djangoproject.com/en/dev/ref/contrib/csrf/#page-uses-ajax-without-any-html-form,
ale musisz poczekać na 1.4, chyba że to tylko dokumentacja nadążająca za kodem

Edytować

Zauważ też, że późniejsze dokumenty Django zauważają błąd w jQuery 1.5, więc upewnij się, że używasz 1.5.1 lub nowszego z sugerowanym kodem Django: http://docs.djangoproject.com/en/1.3/ref/contrib/csrf/# ajax

Steven
źródło
Moja odpowiedź była dokładna w momencie jej pisania :) Było to zaraz po aktualizacji Django z 1.2.4 do 1.2.5. Wtedy też najnowsza wersja jQuery miała 1.5. Okazuje się, że źródłem problemu był błąd jQuery (1.5) i ta informacja jest teraz dodawana do dokumentu Django, jak już wspomniałeś. W moim przypadku: plik cookie został ustawiony, a token NIE został dodany do żądania AJAX. Podana poprawka działała dla błędnego jQuery 1.5. Na razie możesz po prostu trzymać się oficjalnych dokumentów, używając przykładowego kodu tam podanego i używając najnowszego jQuery. Twój problem miał inne źródło niż omawiane tutaj problemy :)
Jakub Gocławski,
2
Istnieje teraz dekorator o nazwie ensure_csrf_cookie, który można owinąć wokół widoku, aby upewnić się, że wysyła ciasteczko.
Brian Neal,
To jest problem, który miałem, csrftokenprzede wszystkim nie ma ciasteczka, dzięki!
crhodes
5

Wygląda na to, że nikt nie wspomniał o tym, jak to zrobić w czystym JS za pomocą X-CSRFTokennagłówka {{ csrf_token }}, więc oto proste rozwiązanie, w którym nie trzeba przeszukiwać plików cookie ani DOM:

var xhttp = new XMLHttpRequest();
xhttp.open("POST", url, true);
xhttp.setRequestHeader("X-CSRFToken", "{{ csrf_token }}");
xhttp.send();
Alex
źródło
4

Ponieważ w bieżących odpowiedziach nie podano nigdzie, najszybszym rozwiązaniem, jeśli nie osadzasz js w szablonie, jest:

Umieść <script type="text/javascript"> window.CSRF_TOKEN = "{{ csrf_token }}"; </script>przed odniesieniem do pliku script.js w szablonie, a następnie dodaj csrfmiddlewaretokendo datasłownika w pliku js:

$.ajax({
            type: 'POST',
            url: somepathname + "do_it/",
            data: {csrfmiddlewaretoken: window.CSRF_TOKEN},
            success: function() {
                console.log("Success!");
            }
        })
Marek Židek
źródło
2

Właśnie spotkałem trochę inną, ale podobną sytuację. Nie jestem w 100% pewien, czy będzie to rozwiązanie twojego przypadku, ale rozwiązałem problem dla Django 1.3, ustawiając parametr POST „csrfmiddlewaretoken” z odpowiednim ciągiem wartości pliku cookie, który jest zwykle zwracany w postaci domowego kodu HTML przez Django system szablonów z tagiem „{% csrf_token%}”. Nie próbowałem na starszym Django, po prostu stało się i rozwiązałem na Django 1.3. Mój problem polegał na tym, że pierwsze żądanie przesłane przez Ajax z formularza zostało pomyślnie wykonane, ale druga próba z dokładnie tego samego z nie powiodła się, spowodowało stan 403, mimo że nagłówek „X-CSRFToken” jest również poprawnie umieszczony z wartością tokena CSRF jak w przypadku pierwszej próby. Mam nadzieję że to pomoże.

Pozdrowienia,

Hiro

Hiroaki Kishimoto
źródło
2

możesz wkleić ten plik js do pliku HTML, pamiętaj, aby umieścić go przed inną funkcją js

<script>
  // using jQuery
  function getCookie(name) {
    var cookieValue = null;
    if (document.cookie && document.cookie != '') {
      var cookies = document.cookie.split(';');
      for (var i = 0; i < cookies.length; i++) {
        var cookie = jQuery.trim(cookies[i]);
        // Does this cookie string begin with the name we want?
        if (cookie.substring(0, name.length + 1) == (name + '=')) {
          cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
          break;
        }
      }
    }
    return cookieValue;
  }

  function csrfSafeMethod(method) {
    // these HTTP methods do not require CSRF protection
    return (/^(GET|HEAD|OPTIONS|TRACE)$/.test(method));
  }

  $(document).ready(function() {
    var csrftoken = getCookie('csrftoken');
    $.ajaxSetup({
      beforeSend: function(xhr, settings) {
        if (!csrfSafeMethod(settings.type) && !this.crossDomain) {
          xhr.setRequestHeader("X-CSRFToken", csrftoken);
        }
      }
    });
  });
</script>

nooobpan
źródło
1

Jeden token CSRF jest przypisany do każdej sesji (tj. Przy każdym logowaniu). Zanim więc chcesz uzyskać dane wprowadzone przez użytkownika i wysłać je jako wywołanie ajax do jakiejś funkcji chronionej przez dekorator csrf_protect, spróbuj znaleźć funkcje, które są wywoływane przed otrzymaniem tych danych od użytkownika. Np. Musi być renderowany szablon, na którym użytkownik wprowadza dane. Ten szablon jest renderowany przez jakąś funkcję. W tej funkcji możesz uzyskać token csrf w następujący sposób: csrf = request.COOKIES ['csrftoken'] Teraz przekaż tę wartość csrf w słowniku kontekstowym, dla którego renderowany jest dany szablon. Teraz w tym szablonie napisz ten wiersz: Teraz w swojej funkcji javascript, przed wysłaniem żądania ajax, napisz to: var csrf = $ ('# csrf'). val () to wybierze wartość tokena przekazanego do szablonu i zapisze go w zmiennej csrf. Teraz podczas wykonywania połączenia ajax w danych postu przekaż również tę wartość: „csrfmiddlewaretoken”: csrf

Będzie to działać, nawet jeśli nie wdrażasz formularzy django.

W rzeczywistości logika jest następująca: Potrzebujesz tokena, który możesz otrzymać z żądania. Musisz więc po prostu dowiedzieć się, jaka funkcja jest wywoływana natychmiast po zalogowaniu. Kiedy już będziesz mieć ten token, albo wykonaj kolejne wywołanie ajax, aby je zdobyć, albo przekaż je do szablonu dostępnego dla twojego ajax.

AMIT PRAKASH PANDEY
źródło
Niezbyt dobrze skonstruowany, ale dobrze wyjaśniony. Mój problem polegał na tym, że wysyłałem csrf w ten sposób: csrftoken: csrftokenzamiast csrfmiddlwaretoken: csrftoken. Po zmianie zadziałało. Dzięki
prawie początkujący
1

dla kogoś, kto zetknie się z tym i próbuje debugować:

1) Kontrola django csrf (zakładając, że ją wysyłasz) jest tutaj

2) W moim przypadku settings.CSRF_HEADER_NAMEustawiono opcję „HTTP_X_CSRFTOKEN”, a moje wywołanie AJAX wysyłało nagłówek o nazwie „HTTP_X_CSRF_TOKEN”, więc rzeczy nie działały. Mógłbym to zmienić w wywołaniu AJAX lub ustawienie django.

3) Jeśli zdecydujesz się zmienić to po stronie serwera, znajdź swoją lokalizację instalacji django i rzuć punkt przerwania w csrf middleware.f używasz virtualenv, będzie to coś w stylu:~/.envs/my-project/lib/python2.7/site-packages/django/middleware/csrf.py

import ipdb; ipdb.set_trace() # breakpoint!!
if request_csrf_token == "":
    # Fall back to X-CSRFToken, to make things easier for AJAX,
    # and possible for PUT/DELETE.
    request_csrf_token = request.META.get(settings.CSRF_HEADER_NAME, '')

Następnie upewnij się, że csrftoken został poprawnie pobrany z żądania.META

4) Jeśli potrzebujesz zmienić nagłówek itp. - zmień tę zmienną w pliku ustawień

daino3
źródło
0

W moim przypadku problem dotyczył konfiguracji nginx, którą skopiowałem z serwera głównego na tymczasowy z wyłączeniem https, który nie jest potrzebny na drugim w tym procesie.

Musiałem skomentować te dwa wiersze w konfiguracji, aby znów działało:

# uwsgi_param             UWSGI_SCHEME    https;
# uwsgi_pass_header       X_FORWARDED_PROTO;
int_ua
źródło
0

Oto mniej szczegółowe rozwiązanie dostarczone przez Django:

<script type="text/javascript">
// using jQuery
var csrftoken = jQuery("[name=csrfmiddlewaretoken]").val();

function csrfSafeMethod(method) {
    // these HTTP methods do not require CSRF protection
    return (/^(GET|HEAD|OPTIONS|TRACE)$/.test(method));
}
// set csrf header
$.ajaxSetup({
    beforeSend: function(xhr, settings) {
        if (!csrfSafeMethod(settings.type) && !this.crossDomain) {
            xhr.setRequestHeader("X-CSRFToken", csrftoken);
        }
    }
});

// Ajax call here
$.ajax({
    url:"{% url 'members:saveAccount' %}",
    data: fd,
    processData: false,
    contentType: false,
    type: 'POST',
    success: function(data) {
        alert(data);
        }
    });
</script>

Źródło: https://docs.djangoproject.com/en/1.11/ref/csrf/

Braden Holt
źródło