Controllo Django CSRF non riuscito con una richiesta POST Ajax


180

Potrei usare un po 'di aiuto per conformarmi al meccanismo di protezione CSRF di Django tramite il mio post AJAX. Ho seguito le indicazioni qui:

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

Ho copiato esattamente il codice di esempio AJAX che hanno su quella pagina:

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

Ho messo un avviso stampando il contenuto di getCookie('csrftoken')prima della xhr.setRequestHeaderchiamata ed è effettivamente popolato con alcuni dati. Non sono sicuro di come verificare che il token sia corretto, ma sono incoraggiato a trovare e inviare qualcosa.

Ma Django sta ancora rifiutando il mio post AJAX.

Ecco il mio JavaScript:

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

Ecco l'errore che vedo da Django:

[23 / feb / 2011 22:08:29] "POST / memorizza / HTTP / 1.1" 403 2332

Sono sicuro che mi manca qualcosa, e forse è semplice, ma non so cosa sia. Ho cercato SO e ho visto alcune informazioni su come disattivare il controllo CSRF per la mia vista tramite il csrf_exemptdecoratore, ma lo trovo poco interessante. L'ho provato e funziona, ma preferirei far funzionare il mio POST nel modo in cui Django è stato progettato per aspettarlo, se possibile.

Nel caso sia utile, ecco l'essenza di ciò che la mia vista sta facendo:

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

Grazie per le tue risposte!


1
Quale versione di Django stai usando?
zsquare,

Hai aggiunto le classi middleware CSRF corrette e le hai messe nell'ordine corretto?
darren,

Jakub ha risposto alla mia domanda di seguito, ma nel caso sia utile ad altre persone: @zsquare: versione 1.2.3. @mongoose_za: Sì, vengono aggiunti e nel giusto ordine.
firebush,

Risposte:


181

Vera soluzione

Ok, sono riuscito a rintracciare il problema. Si trova nel codice Javascript (come ho suggerito di seguito).

Ciò di cui hai bisogno è questo:

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

invece del codice pubblicato nei documenti ufficiali: https://docs.djangoproject.com/en/2.2/ref/csrf/

Il codice di lavoro proviene da questa voce di Django: http://www.djangoproject.com/weblog/2011/feb/08/security/

Quindi la soluzione generale è: "usa il gestore ajaxSetup invece del gestore ajaxSend". Non so perché funzioni. Ma funziona per me :)

Post precedente (senza risposta)

In realtà sto riscontrando lo stesso problema.

Si verifica dopo l'aggiornamento a Django 1.2.5 - non si sono verificati errori con le richieste POST AJAX in Django 1.2.4 (AJAX non era protetto in alcun modo, ma funzionava perfettamente).

Proprio come OP, ho provato lo snippet JavaScript pubblicato nella documentazione di Django. Sto usando jQuery 1.5. Sto anche usando il middleware "django.middleware.csrf.CsrfViewMiddleware".

Ho provato a seguire il codice del middleware e so che non riesce su questo:

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

e poi

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

questo "if" è vero, perché "request_csrf_token" è vuoto.

Fondamentalmente significa che l'intestazione NON è impostata. Quindi c'è qualcosa di sbagliato in questa linea JS:

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

?

Spero che i dettagli forniti ci possano aiutare a risolvere il problema :)


Questo ha funzionato! Ho inserito la funzione .ajaxSetup mentre l'hai incollata sopra e ora sono in grado di pubblicare senza un errore 403. Grazie per aver condiviso la soluzione, Jakub. Buona scoperta. :)
firebush,

Usare ajaxSetupinvece di ajaxSendandare in contrasto con i documenti jQuery: api.jquery.com/jQuery.ajaxSetup
Mark Lavin

usando 1.3, la documentazione ufficiale di django ha funzionato per me.
monkut,

1
Ho provato ma questo non sembra funzionare per me, sto usando jQuery v1.7.2, la mia domanda è stackoverflow.com/questions/11812694/…
daydreamer

Devo aggiungere l'annotazione @ensure_csrf_cookie alla mia funzione di visualizzazione per forzare l'impostazione del cookie csrf quando la pagina viene richiesta dai dispositivi mobili.
Kane,

172

Se si utilizza la $.ajaxfunzione, è possibile semplicemente aggiungere il csrftoken nel corpo dati:

$.ajax({
    data: {
        somedata: 'somedata',
        moredata: 'moredata',
        csrfmiddlewaretoken: '{{ csrf_token }}'
    },

2
quando uso la risposta contrassegnata funziona per me, ma se uso la tua soluzione qui non funziona. Ma la tua soluzione dovrebbe funzionare, non capisco perché non lo sia. C'è qualcos'altro che deve essere fatto in Django 1.4?
Houman,

1
Grazie! così semplice. Lavora ancora su Django 1.8 e jquery 2.1.3
Alejandro Veintimilla,

19
Questa soluzione richiede che il javascript sia incorporato nel modello vero?
Mox,

15
@Mox: mettilo in html, ma sopra il tuo file Js dove c'è una funzione ajax <script type="text/javascript"> window.CSRF_TOKEN = "{{ csrf_token }}"; </script>
HereHere

Grazie! Così semplice ed elegante. Funziona per me con Django 1.8. Ho aggiunto csrfmiddlewaretoken: '{{ csrf_token }}'al mio datadizionario in una $.postchiamata.
Pavel Yudaev,

75

Aggiungi questa riga al tuo codice jQuery:

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

e fatto.


Ho provato questo, tranne per il fatto che il mio modulo ha un caricamento di file. Il mio backend è Django e ancora l'errore 400CSRF Failed: CSRF token missing or incorrect.
Hussain,

16

Il problema è perché django si aspetta che il valore del cookie venga restituito come parte dei dati del modulo. Il codice della risposta precedente sta ottenendo JavaScript per cercare il valore del cookie e inserirlo nei dati del modulo. È un modo adorabile di farlo da un punto di vista tecnico, ma sembra un po 'prolisso.

In passato, l'ho fatto più semplicemente ottenendo il javascript per inserire il valore del token nei dati di post.

Se si utilizza {% csrf_token%} nel modello, verrà emesso un campo modulo nascosto che porta il valore. Ma se usi {{csrf_token}} otterrai solo il valore nudo del token, quindi puoi usarlo in javascript in questo modo ....

csrf_token = "{{ csrf_token }}";

Quindi puoi includerlo, con il nome chiave richiesto nell'hash che invii come dati alla chiamata ajax.


@aehlke Puoi avere file statici. Nel codice sorgente, si può vedere un bel esempio, dove si registra Django variabili in windowoggetto, in modo che siano accessibili in seguito. Anche nei file statici.
KitKat,

3
@KitKat in effetti :) Mi dispiace per il mio commento antico e ignorante qui. Buon punto.
aehlke,

re file statici. Non è un problema, se non ti dispiace un po 'di js il tuo html. Ho appena inserito {{csrf_token}} nel modello html principale, non lontano dagli incantesimi requisiti. ha funzionato come un fascino.
JL Peyret,

14

I {% csrf_token %}modelli HTML inseriti all'interno<form></form>

si traduce in qualcosa del tipo:

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

quindi perché non farlo semplicemente nel tuo JS in questo modo:

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

e poi passalo ad esempio facendo un POST, come:

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

11

Risposta non jquery:

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

utilizzo:

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

8

Se il tuo modulo viene pubblicato correttamente in Django senza JS, dovresti essere in grado di migliorarlo progressivamente con ajax senza alcun hacking o passaggio disordinato del token csrf. Basta serializzare l'intero modulo e questo raccoglierà automaticamente tutti i campi del tuo modulo incluso il campo nascosto 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;
});

Ho provato questo con Django 1.3+ e jQuery 1.5+. Ovviamente questo funzionerà con qualsiasi modulo HTML, non solo con le app Django.


5

Usa Firefox con Firebug. Apri la scheda "Console" mentre esegui la richiesta Ajax. Con DEBUG=Truete ottieni la bella pagina di errore di django come risposta e puoi anche vedere l'html renderizzato della risposta ajax nella scheda della console.

Quindi saprai qual è l'errore.


5

La risposta accettata è molto probabilmente un'aringa rossa. La differenza tra Django 1.2.4 e 1.2.5 era il requisito per un token CSRF per le richieste AJAX.

Mi sono imbattuto in questo problema su Django 1.3 ed è stato causato in primo luogo dal cookie CSRF . Django non imposterà il cookie a meno che non sia necessario. Quindi un sito ajax esclusivamente o pesantemente in esecuzione su Django 1.2.4 potenzialmente non avrebbe mai inviato un token al client e quindi l'aggiornamento che richiede il token causerebbe gli errori 403.

La soluzione ideale è qui: http://docs.djangoproject.com/en/dev/ref/contrib/csrf/#page-uses-ajax-without-any-html-form
ma dovresti aspettare 1.4 a meno che questa è solo la documentazione che sta raggiungendo il codice

modificare

Nota anche che i successivi documenti Django rilevano un bug in jQuery 1.5, quindi assicurati di utilizzare 1.5.1 o versioni successive con il codice suggerito Django: http://docs.djangoproject.com/en/1.3/ref/contrib/csrf/# ajax


La mia risposta era accurata al momento della stesura di questo :) Fu subito dopo l'aggiornamento di Django dalla 1.2.4 alla 1.2.5. È stato anche quando la versione jQuery più recente era 1.5. Si scopre che la fonte del problema è stata risolta con jQuery (1.5) e queste informazioni vengono ora aggiunte al documento Django, come hai affermato. Nel mio caso: il cookie È STATO impostato e il token NON è stato aggiunto alla richiesta AJAX. La correzione fornita ha funzionato per jQuery 1.5. A partire da ora, puoi semplicemente attenerti ai documenti ufficiali, usando il codice di esempio fornito lì e utilizzando il jQuery più recente. Il tuo problema aveva una fonte diversa rispetto ai problemi discussi qui :)
Jakub Gocławski,

2
Ora c'è un decoratore chiamato ensure_csrf_cookieche puoi avvolgere una vista per assicurarti che invii il cookie.
Brian Neal

Questo è il problema che ho riscontrato, csrftokenin primo luogo non ci sono cookie, grazie!
cripte

5

Sembra che nessuno abbia menzionato come farlo in puro JS usando l' X-CSRFTokenintestazione e {{ csrf_token }}, quindi ecco una semplice soluzione in cui non è necessario cercare tra i cookie o il DOM:

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

4

Poiché non è indicato da nessuna parte nelle risposte correnti, la soluzione più veloce se non si incorpora js nel tuo modello è:

Inserisci <script type="text/javascript"> window.CSRF_TOKEN = "{{ csrf_token }}"; </script>prima il tuo riferimento al file script.js nel tuo modello, quindi aggiungi csrfmiddlewaretokennel datadizionario nel tuo file js:

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

2

Ho appena incontrato una situazione un po 'diversa ma simile. Non sono sicuro al 100% se si tratterebbe di una risoluzione del tuo caso, ma ho risolto il problema per Django 1.3 impostando un parametro POST 'csrfmiddlewaretoken' con la stringa del valore del cookie corretta che di solito viene restituita sotto forma di HTML della tua casa da Django sistema di template con tag '{% csrf_token%}'. Non ho provato il vecchio Django, è appena successo e risolto su Django1.3. Il mio problema era che la prima richiesta inviata tramite Ajax da un modulo era stata eseguita correttamente ma il secondo tentativo dallo stesso esatto da fallito, ha portato allo stato 403 anche se l'intestazione 'X-CSRFToken' è correttamente posizionata anche con il valore token CSRF come nel caso del primo tentativo. Spero che questo ti aiuti.

Saluti,

Hiro


2

puoi incollare questo js nel tuo file html, ricorda di metterlo prima di altre funzioni 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>


1

Un token CSRF è assegnato a ogni sessione (cioè ogni volta che accedi). Quindi, prima di voler ottenere alcuni dati immessi dall'utente e inviarli come chiamata ajax a una funzione che è protetta da csrf_protect decorator, prova a trovare le funzioni che vengono chiamate prima di ottenere questi dati dall'utente. Ad esempio, è necessario eseguire il rendering di alcuni modelli su cui l'utente sta inserendo i dati. Quel modello viene reso da una funzione. In questa funzione è possibile ottenere il token csrf nel modo seguente: csrf = request.COOKIES ['csrftoken'] Ora passare questo valore csrf nel dizionario di contesto in base al quale viene visualizzato il modello in questione. Ora in quel modello scrivi questa riga: Ora nella tua funzione JavaScript, prima di fare una richiesta Ajax, scrivi questo: var csrf = $ ('# csrf'). val () questo prenderà il valore del token passato al template e lo memorizzerà nella variabile csrf. Ora mentre fai una chiamata Ajax, nei tuoi dati post, passa anche questo valore: "csrfmiddlewaretoken": csrf

Funzionerà anche se non stai implementando i moduli Django.

In effetti, la logica qui è: è necessario un token che è possibile ottenere dalla richiesta. Quindi devi solo capire la funzione chiamata immediatamente dopo l'accesso. Una volta che hai questo token, esegui un'altra chiamata ajax per ottenerlo o passalo a un modello accessibile da ajax.


Non molto ben strutturato, ma ben spiegato. Il mio problema era che stavo inviando csrf in questo modo:, csrftoken: csrftokenpiuttosto che csrfmiddlwaretoken: csrftoken. Dopo il cambiamento, ha funzionato. Grazie
quasi un principiante il

1

per qualcuno che si imbatte in questo e sta cercando di eseguire il debug:

1) il controllo django csrf (supponendo che tu stia inviando uno) è qui

2) Nel mio caso, settings.CSRF_HEADER_NAMEera impostato su "HTTP_X_CSRFTOKEN" e la mia chiamata AJAX stava inviando un'intestazione denominata "HTTP_X_CSRF_TOKEN", quindi le cose non funzionavano. Potrei cambiarlo nella chiamata AJAX, o impostazione django.

3) Se scegli di cambiarlo sul lato server, trova la posizione di installazione di django e lancia un punto di interruzione nel csrf middleware.f che stai utilizzando virtualenv, sarà qualcosa del tipo:~/.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, '')

Quindi, assicurarsi che il csrftoken provenga correttamente da request.META

4) Se è necessario modificare l'intestazione, ecc., Modificare quella variabile nel file delle impostazioni



0

Nel mio caso il problema era con la configurazione nginx che ho copiato dal server principale a uno temporaneo con disabilitazione di https che non è necessario sul secondo nel processo.

Ho dovuto commentare queste due righe nella configurazione per farlo funzionare di nuovo:

# uwsgi_param             UWSGI_SCHEME    https;
# uwsgi_pass_header       X_FORWARDED_PROTO;

0

Ecco una soluzione meno dettagliata fornita da 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>

Fonte: https://docs.djangoproject.com/en/1.11/ref/csrf/

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.