Django CSRF-Prüfung schlägt mit einer Ajax-POST-Anforderung fehl

180

Ich könnte über meinen AJAX-Beitrag Hilfe bei der Einhaltung des CSRF-Schutzmechanismus von Django gebrauchen. Ich habe die Anweisungen hier befolgt:

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

Ich habe den AJAX-Beispielcode, den sie auf dieser Seite haben, genau kopiert:

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

Ich habe eine Warnung eingefügt, die den Inhalt getCookie('csrftoken')vor dem xhr.setRequestHeaderAnruf druckt, und er ist tatsächlich mit einigen Daten gefüllt. Ich bin nicht sicher, wie ich überprüfen soll, ob das Token korrekt ist, aber ich bin ermutigt, etwas zu finden und zu senden.

Aber Django lehnt meinen AJAX-Beitrag immer noch ab.

Hier ist mein JavaScript:

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

Hier ist der Fehler, den ich von Django sehe:

[23 / Feb / 2011 22:08:29] "POST / memorize / HTTP / 1.1" 403 2332

Ich bin mir sicher, dass mir etwas fehlt, und vielleicht ist es einfach, aber ich weiß nicht, was es ist. Ich habe mich in SO umgesehen und einige Informationen zum Deaktivieren der CSRF-Prüfung für meine Ansicht über das Internet angezeigtcsrf_exempt Dekorateur gesehen, aber ich finde das unattraktiv. Ich habe das ausprobiert und es funktioniert, aber ich möchte lieber, dass mein POST so funktioniert, wie Django es erwartet, wenn möglich.

Nur für den Fall, dass es hilfreich ist, hier ist der Kern meiner Ansicht:

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))

Vielen Dank für Ihre Antworten!

Feuerbusch
quelle
1
Welche Version von Django benutzt du?
Zsquare
Haben Sie die richtigen CSRF-Middleware-Klassen hinzugefügt und in die richtige Reihenfolge gebracht?
Darren
Jakub beantwortete meine Frage unten, aber nur für den Fall, dass es für andere nützlich ist: @zsquare: Version 1.2.3. @mongoose_za: Ja, sie werden hinzugefügt und in der richtigen Reihenfolge.
Firebush

Antworten:

181

Echte Lösung

Ok, ich habe es geschafft, das Problem aufzuspüren. Es liegt im Javascript-Code (wie ich unten vorgeschlagen habe).

Was Sie brauchen, ist Folgendes:

$.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'));
         }
     } 
});

anstelle des Codes in den offiziellen Dokumenten: https://docs.djangoproject.com/de/2.2/ref/csrf/

Der Arbeitscode stammt aus diesem Django-Eintrag: http://www.djangoproject.com/weblog/2011/feb/08/security/

Die allgemeine Lösung lautet also: "Verwenden Sie den AjaxSetup-Handler anstelle des AjaxSend-Handlers." Ich weiß nicht warum es funktioniert. Aber es funktioniert bei mir :)

Vorheriger Beitrag (ohne Antwort)

Ich habe tatsächlich das gleiche Problem.

Es tritt nach dem Update auf Django 1.2.5 auf - es gab keine Fehler mit AJAX POST-Anforderungen in Django 1.2.4 (AJAX war in keiner Weise geschützt, aber es funktionierte einwandfrei).

Genau wie bei OP habe ich das in der Django-Dokumentation veröffentlichte JavaScript-Snippet ausprobiert. Ich verwende jQuery 1.5. Ich verwende auch die Middleware "django.middleware.csrf.CsrfViewMiddleware".

Ich habe versucht, dem Middleware-Code zu folgen, und ich weiß, dass dies fehlschlägt:

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

und dann

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

Dieses "wenn" ist wahr, weil "request_csrf_token" leer ist.

Grundsätzlich bedeutet dies, dass der Header NICHT gesetzt ist. Stimmt etwas mit dieser JS-Zeile nicht:

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

?

Ich hoffe, dass die angegebenen Details uns bei der Lösung des Problems helfen werden :)

Jakub Gocławski
quelle
Das hat funktioniert! Ich habe die .ajaxSetup-Funktion so eingefügt, wie Sie sie oben eingefügt haben, und kann jetzt ohne 403-Fehler posten. Vielen Dank, dass Sie die Lösung geteilt haben, Jakub. Guter Fund. :)
Firebush
Verwenden von ajaxSetupanstatt zu ajaxSendlaufen entgegen den jQuery-Dokumenten: api.jquery.com/jQuery.ajaxSetup
Mark Lavin
Mit 1.3 funktionierte der offizielle Django-Dokumentationseintrag für mich.
Mönch
1
Ich habe es versucht, aber das scheint bei mir nicht zu funktionieren. Ich verwende jQuery v1.7.2. Meine Frage lautet stackoverflow.com/questions/11812694/…
Tagträumer
Ich muss meiner Ansichtsfunktion die Anmerkung @ensure_csrf_cookie hinzufügen , um das Setzen des CSRF- Cookies zu erzwingen, wenn die Seite von Mobilgeräten angefordert wird.
Kane
167

Wenn Sie die $.ajaxFunktion verwenden, können Sie das csrfToken einfach in den Datenkörper einfügen:

$.ajax({
    data: {
        somedata: 'somedata',
        moredata: 'moredata',
        csrfmiddlewaretoken: '{{ csrf_token }}'
    },
Bryan
quelle
2
Wenn ich die markierte Antwort verwende, funktioniert es für mich, aber wenn ich Ihre Lösung hier verwende, funktioniert es nicht. Aber Ihre Lösung sollte funktionieren, ich verstehe nicht, warum es nicht funktioniert. Gibt es noch etwas, das in Django 1.4 getan werden muss?
Houman
1
Vielen Dank! so einfach. Arbeitet immer noch auf Django 1.8 und JQuery 2.1.3
Alejandro Veintimilla
19
Diese Lösung erfordert, dass das Javascript in die Vorlage eingebettet ist, nicht wahr?
Mox
15
@Mox: Fügen Sie dies in HTML, aber über Ihrer Js-Datei, wo sich eine Ajax-Funktion befindet <script type="text/javascript"> window.CSRF_TOKEN = "{{ csrf_token }}"; </script>
HereHere
Vielen Dank! So einfach und elegant. Es funktioniert bei mir mit Django 1.8. Ich csrfmiddlewaretoken: '{{ csrf_token }}'habe mein dataWörterbuch in einem $.postAnruf hinzugefügt .
Pavel Yudaev
75

Fügen Sie diese Zeile Ihrem jQuery-Code hinzu:

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

und fertig.

Kambiz
quelle
Ich habe es versucht, außer mein Formular hat einen Datei-Upload. Mein Backend ist Django und bekomme immer noch Fehler 400CSRF Failed: CSRF token missing or incorrect.
Hussain
16

Das Problem ist, dass Django erwartet, dass der Wert aus dem Cookie als Teil der Formulardaten zurückgegeben wird. Der Code aus der vorherigen Antwort erhält Javascript, um den Cookie-Wert zu ermitteln und in die Formulardaten einzufügen. Das ist eine schöne Art, es aus technischer Sicht zu tun, aber es sieht ein bisschen ausführlich aus.

In der Vergangenheit habe ich es einfacher gemacht, indem ich das Javascript dazu gebracht habe, den Token-Wert in die Post-Daten einzufügen.

Wenn Sie in Ihrer Vorlage {% csrf_token%} verwenden, wird ein ausgeblendetes Formularfeld ausgegeben, das den Wert enthält. Wenn Sie jedoch {{csrf_token}} verwenden, erhalten Sie nur den bloßen Wert des Tokens, sodass Sie diesen in Javascript wie diesem verwenden können.

csrf_token = "{{ csrf_token }}";

Dann können Sie das mit dem erforderlichen Schlüsselnamen in den Hash aufnehmen, den Sie dann als Daten an den Ajax-Aufruf senden.

fatgeekuk
quelle
@aehlke Sie können statische Dateien haben. Im Quellcode sehen Sie ein schönes Beispiel, in dem Sie Django-Variablen im windowObjekt registrieren , damit Sie anschließend darauf zugreifen können . Auch in statischen Dateien.
KitKat
3
@ KitKat in der Tat :) Entschuldigung für meinen alten, unwissenden Kommentar hier. Guter Punkt.
Aehlke
re statische Dateien. Kein Problem, wenn es Ihnen nichts ausmacht, ein bisschen von Ihrem HTML zu lesen. Ich habe gerade {{csrf_token}} in die HTML-Hauptvorlage eingefügt, nicht weit von den Beschwörungsformeln entfernt. Lief wie am Schnürchen.
JL Peyret
14

Die {% csrf_token %}darin enthaltenen HTML-Vorlagen<form></form>

übersetzt zu so etwas wie:

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

Warum grepst du es nicht einfach so in deinem JS:

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

und dann weitergeben, zB einen POST machen, wie:

$.post( "/panel/change_password/", {foo: bar, csrfmiddlewaretoken: token}, function(data){
    console.log(data);
});
Andilabs
quelle
9

Non-jquery Antwort:

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;
};

Verwendung:

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);

quelle
8

Wenn Ihr Formular in Django ohne JS korrekt veröffentlicht wird, sollten Sie es schrittweise mit Ajax erweitern können, ohne dass das CSRF-Token gehackt oder unordentlich weitergegeben wird. Serialisieren Sie einfach das gesamte Formular und das wird automatisch alle Ihre Formularfelder einschließlich des versteckten csrf-Felds aufnehmen:

$('#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;
});

Ich habe dies mit Django 1.3+ und jQuery 1.5+ getestet. Dies funktioniert natürlich für jedes HTML-Formular, nicht nur für Django-Apps.

GivP
quelle
5

Verwenden Sie Firefox mit Firebug. Öffnen Sie die Registerkarte "Konsole", während Sie die Ajax-Anforderung auslösen. Mit erhalten DEBUG=TrueSie die nette Django-Fehlerseite als Antwort und Sie können sogar das gerenderte HTML der Ajax-Antwort auf der Registerkarte Konsole sehen.

Dann wissen Sie, was der Fehler ist.

Jammon
quelle
5

Die akzeptierte Antwort ist höchstwahrscheinlich ein roter Hering. Der Unterschied zwischen Django 1.2.4 und 1.2.5 bestand in der Anforderung eines CSRF-Tokens für AJAX-Anforderungen.

Ich bin auf dieses Problem bei Django 1.3 gestoßen und es wurde dadurch verursacht, dass das CSRF-Cookie überhaupt nicht gesetzt wurde. Django setzt den Cookie nur, wenn dies erforderlich ist. Eine ausschließlich oder stark ajax-Site, die unter Django 1.2.4 ausgeführt wird, hätte möglicherweise niemals ein Token an den Client gesendet, und das Upgrade, für das das Token erforderlich ist, würde die 403-Fehler verursachen.

Die ideale Lösung finden Sie hier: http://docs.djangoproject.com/de/dev/ref/contrib/csrf/#page-uses-ajax-without-any-html-form,
aber Sie müssten auf 1.4 warten, es sei denn Dies ist nur eine Dokumentation, die den Code einholt

Bearbeiten

Beachten Sie auch, dass die späteren Django-Dokumente einen Fehler in jQuery 1.5 feststellen. Stellen Sie daher sicher, dass Sie 1.5.1 oder höher mit dem von Django vorgeschlagenen Code verwenden: http://docs.djangoproject.com/de/1.3/ref/contrib/csrf/# Ajax

Steven
quelle
Meine Antwort war zum Zeitpunkt des Schreibens korrekt :) Es war kurz nachdem Django von 1.2.4 auf 1.2.5 aktualisiert wurde. Es war auch, als die neueste jQuery-Version 1.5 war. Es stellt sich heraus, dass die Ursache des Problems jQuery (1.5) fehlerhaft war und diese Informationen nun, wie Sie angegeben haben, zu Django doc hinzugefügt werden. In meinem Fall: Das Cookie wurde gesetzt und das Token wurde NICHT zur AJAX-Anforderung hinzugefügt. Gegebenes Update funktionierte für fehlerhaftes jQuery 1.5. Ab sofort können Sie sich einfach an die offiziellen Dokumente halten, indem Sie den dort angegebenen Beispielcode und die neueste jQuery verwenden. Ihr Problem hatte eine andere Quelle als die hier diskutierten Probleme :)
Jakub Gocławski
2
Es gibt jetzt einen Dekorateur namens ensure_csrf_cookie, mit dem Sie eine Ansicht umschließen können, um sicherzustellen, dass der Cookie gesendet wird.
Brian Neal
Dies ist das Problem, das ich hatte, es gibt überhaupt keinen csrftokenCookie, danke!
Crhodes
5

Es scheint, dass niemand erwähnt hat, wie dies in reinem JS mithilfe des X-CSRFTokenHeaders gemacht werden {{ csrf_token }}soll. Hier ist eine einfache Lösung, bei der Sie weder die Cookies noch das DOM durchsuchen müssen:

var xhttp = new XMLHttpRequest();
xhttp.open("POST", url, true);
xhttp.setRequestHeader("X-CSRFToken", "{{ csrf_token }}");
xhttp.send();
Alex
quelle
4

Da dies in den aktuellen Antworten nirgends angegeben ist, ist die schnellste Lösung, wenn Sie js nicht in Ihre Vorlage einbetten, folgende:

<script type="text/javascript"> window.CSRF_TOKEN = "{{ csrf_token }}"; </script>Fügen Sie vor Ihren Verweis auf die Datei script.js in Ihre Vorlage ein und fügen csrfmiddlewaretokenSie sie dann in Ihr dataWörterbuch in Ihrer js-Datei ein:

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

Ich habe gerade eine etwas andere, aber ähnliche Situation erlebt. Ich bin mir nicht 100% sicher, ob es sich um eine Lösung für Ihren Fall handelt, aber ich habe das Problem für Django 1.3 behoben, indem ich einen POST-Parameter 'csrfmiddlewaretoken' mit der richtigen Cookie-Wertzeichenfolge festgelegt habe, die normalerweise von Django in Form Ihres Home-HTML zurückgegeben wird Vorlagensystem mit dem Tag '{% csrf_token%}'. Ich habe den älteren Django nicht anprobiert, bin einfach passiert und habe mich für Django1.3 entschieden. Mein Problem war, dass die erste Anfrage, die über Ajax von einem Formular gesendet wurde, erfolgreich ausgeführt wurde, aber der zweite Versuch von genau demselben fehlgeschlagen führte, führte zu einem 403-Status, obwohl der Header 'X-CSRFToken' auch korrekt mit dem CSRF-Token-Wert platziert wurde wie im Fall des ersten Versuchs. Hoffe das hilft.

Grüße,

Hiro

Hiroaki Kishimoto
quelle
2

Sie können dieses js in Ihre HTML-Datei einfügen. Denken Sie daran, es vor andere js-Funktionen zu stellen

<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
quelle
1

Jeder Sitzung wird ein CSRF-Token zugewiesen (dh jedes Mal, wenn Sie sich anmelden). Bevor Sie also Daten vom Benutzer eingeben lassen und diese als Ajax-Aufruf an eine Funktion senden möchten, die durch csrf_protect decorator geschützt ist, versuchen Sie, die Funktionen zu finden, die aufgerufen werden, bevor Sie diese Daten vom Benutzer erhalten. Beispielsweise muss eine Vorlage gerendert werden, für die Ihr Benutzer Daten eingibt. Diese Vorlage wird von einer Funktion gerendert. In dieser Funktion können Sie das csrf-Token wie folgt abrufen: csrf = request.COOKIES ['csrftoken'] Übergeben Sie nun diesen csrf-Wert im Kontextwörterbuch, für das die betreffende Vorlage gerendert wird. Schreiben Sie nun in diese Vorlage diese Zeile: Schreiben Sie nun in Ihrer Javascript-Funktion, bevor Sie eine Ajax-Anfrage stellen, Folgendes: var csrf = $ ('# csrf'). val () wählt den Wert des an die Vorlage übergebenen Tokens aus und speichert ihn in der Variablen csrf. Übergeben Sie jetzt beim Ajax-Aufruf in Ihren Post-Daten auch diesen Wert: "csrfmiddlewaretoken": csrf

Dies funktioniert auch dann, wenn Sie keine Django-Formulare implementieren.

Tatsächlich lautet die Logik hier: Sie benötigen ein Token, das Sie auf Anfrage erhalten können. Sie müssen also nur herausfinden, welche Funktion unmittelbar nach der Anmeldung aufgerufen wird. Wenn Sie dieses Token haben, führen Sie entweder einen weiteren Ajax-Aufruf durch, um es abzurufen, oder übergeben Sie es an eine Vorlage, auf die Ihr Ajax zugreifen kann.

AMIT PRAKASH PANDEY
quelle
Nicht sehr gut strukturiert, aber gut erklärt. Mein Problem war, ich habe csrf auf diese Weise gesendet: csrftoken: csrftokenanstatt csrfmiddlwaretoken: csrftoken. Nach der Änderung hat es funktioniert. Danke
fast ein Anfänger
1

für jemanden, der darauf stößt und versucht zu debuggen:

1) Der Django-CSRF-Check (vorausgesetzt, Sie senden einen) ist hier

2) In meinem Fall settings.CSRF_HEADER_NAME wurde "HTTP_X_CSRFTOKEN" gesetzt und mein AJAX-Aufruf sendete einen Header mit dem Namen "HTTP_X_CSRF_TOKEN", sodass nichts funktionierte. Ich könnte es entweder im AJAX-Aufruf oder in der Django-Einstellung ändern.

3) Wenn Sie sich dafür entscheiden, es serverseitig zu ändern, suchen Sie Ihren Installationsort für django und setzen Sie einen Haltepunkt in die von csrf middlewareIhnen verwendete .f. Dies ist ungefähr virtualenvso:~/.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, '')

Stellen Sie dann sicher, dass die csrfStellen Sie Token korrekt aus request.META bezogen wurde

4) Wenn Sie Ihren Header usw. ändern müssen, ändern Sie diese Variable in Ihrer Einstellungsdatei

daino3
quelle
0

In meinem Fall lag das Problem bei der Nginx-Konfiguration, die ich vom Hauptserver auf einen temporären Server kopiert habe, wobei https deaktiviert wurde, das auf dem zweiten Server nicht benötigt wird.

Ich musste diese beiden Zeilen in der Konfiguration auskommentieren, damit es wieder funktioniert:

# uwsgi_param             UWSGI_SCHEME    https;
# uwsgi_pass_header       X_FORWARDED_PROTO;
int_ua
quelle
0

Hier ist eine weniger ausführliche Lösung von 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>

Quelle: https://docs.djangoproject.com/de/1.11/ref/csrf/

Braden Holt
quelle