Échec de la vérification de Django CSRF avec une requête Ajax POST

180

Je pourrais utiliser un peu d'aide pour me conformer au mécanisme de protection CSRF de Django via mon article AJAX. J'ai suivi les instructions ici:

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

J'ai copié exactement l'exemple de code AJAX qu'ils ont sur cette page:

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

J'ai mis une alerte imprimant le contenu de getCookie('csrftoken')avant l' xhr.setRequestHeaderappel et elle est en effet peuplée de quelques données. Je ne sais pas comment vérifier que le jeton est correct, mais je suis encouragé qu'il trouve et envoie quelque chose.

Mais Django rejette toujours mon message AJAX.

Voici mon JavaScript:

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

Voici l'erreur que je vois de Django:

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

Je suis sûr qu'il me manque quelque chose, et c'est peut-être simple, mais je ne sais pas ce que c'est. J'ai cherché autour de SO et j'ai vu des informations sur la désactivation du contrôle CSRF pour ma vue via le csrf_exemptdécorateur, mais je trouve cela peu attrayant. J'ai essayé cela et cela fonctionne, mais je préfère que mon POST fonctionne comme Django a été conçu pour s'y attendre, si possible.

Juste au cas où cela serait utile, voici l'essentiel de ce que fait mon point de vue:

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

Merci pour vos réponses!

firebush
la source
1
Quelle version de django utilisez-vous?
zsquare
Avez-vous ajouté les bonnes classes middleware CSRF et les avez-vous placées dans le bon ordre?
darren
Jakub a répondu à ma question ci-dessous, mais juste au cas où cela serait utile à d'autres personnes: @zsquare: version 1.2.3. @mongoose_za: Oui, ils sont ajoutés et dans le bon ordre.
firebush

Réponses:

181

Une vraie solution

Ok, j'ai réussi à retracer le problème. Il réside dans le code Javascript (comme je l'ai suggéré ci-dessous).

Voici ce dont vous avez besoin:

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

au lieu du code publié dans la documentation officielle: https://docs.djangoproject.com/en/2.2/ref/csrf/

Le code de travail, provient de cette entrée Django: http://www.djangoproject.com/weblog/2011/feb/08/security/

La solution générale est donc: "utiliser le gestionnaire ajaxSetup au lieu du gestionnaire ajaxSend". Je ne sais pas pourquoi ça marche. Mais cela fonctionne pour moi :)

Article précédent (sans réponse)

Je rencontre le même problème en fait.

Cela se produit après la mise à jour vers Django 1.2.5 - il n'y avait pas d'erreurs avec les requêtes AJAX POST dans Django 1.2.4 (AJAX n'était en aucun cas protégé, mais cela fonctionnait très bien).

Tout comme OP, j'ai essayé l'extrait de code JavaScript publié dans la documentation Django. J'utilise jQuery 1.5. J'utilise également le middleware "django.middleware.csrf.CsrfViewMiddleware".

J'ai essayé de suivre le code du middleware et je sais que cela échoue:

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

puis

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

ce "si" est vrai, car "request_csrf_token" est vide.

Fondamentalement, cela signifie que l'en-tête n'est PAS défini. Alors, y a-t-il quelque chose qui ne va pas avec cette ligne JS:

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

?

J'espère que les détails fournis nous aideront à résoudre le problème :)

Jakub Gocławski
la source
Cela a fonctionné! J'ai mis la fonction .ajaxSetup comme vous l'avez collée ci-dessus et je suis maintenant en mesure de publier sans erreur 403. Merci d'avoir partagé la solution, Jakub. Bonne trouvaille. :)
firebush
Utiliser ajaxSetupplutôt que ajaxSendva à l'encontre de la documentation jQuery: api.jquery.com/jQuery.ajaxSetup
Mark Lavin
en utilisant 1.3, l'entrée de documentation officielle de django a fonctionné pour moi.
monkut
1
J'ai essayé mais cela ne semble pas fonctionner pour moi, j'utilise jQuery v1.7.2, ma question est stackoverflow.com/questions/11812694/...
daydreamer
Je dois ajouter l'annotation @ensure_csrf_cookie à ma fonction de vue pour forcer le cookie csrf lorsque la page est demandée à partir d'appareils mobiles.
Kane
172

Si vous utilisez la $.ajaxfonction, vous pouvez simplement ajouter le csrfjeton dans le corps de données:

$.ajax({
    data: {
        somedata: 'somedata',
        moredata: 'moredata',
        csrfmiddlewaretoken: '{{ csrf_token }}'
    },
Bryan
la source
2
lorsque j'utilise la réponse marquée, cela fonctionne pour moi, mais si j'utilise votre solution ici, ce n'est pas le cas. Mais votre solution devrait fonctionner, je ne comprends pas pourquoi elle ne fonctionne pas. Y a-t-il autre chose à faire dans Django 1.4?
Houman
1
Merci! si simple. Fonctionne toujours sur django 1.8 et jquery 2.1.3
Alejandro Veintimilla
19
Cette solution nécessite que le javascript soit intégré dans le modèle n'est-ce pas?
Mox
15
@Mox: Mettez ceci en html, mais au-dessus de votre fichier Js où se trouve une fonction ajax <script type="text/javascript"> window.CSRF_TOKEN = "{{ csrf_token }}"; </script>
HereHere
Merci! Si simple et élégant. Cela fonctionne pour moi avec Django 1.8. J'ai ajouté csrfmiddlewaretoken: '{{ csrf_token }}'à mon datadictionnaire lors d'un $.postappel.
Pavel Yudaev
75

Ajoutez cette ligne à votre code jQuery:

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

Et.. Voila.

Kambiz
la source
J'ai essayé ceci, sauf que mon formulaire a un téléchargement de fichier. Mon backend est django et j'ai toujours l'erreur 400CSRF Failed: CSRF token missing or incorrect.
Hussain
16

Le problème est que django s'attend à ce que la valeur du cookie soit renvoyée dans le cadre des données du formulaire. Le code de la réponse précédente demande à javascript de rechercher la valeur du cookie et de la placer dans les données du formulaire. C'est une belle façon de le faire d'un point de vue technique, mais cela semble un peu verbeux.

Dans le passé, je l'ai fait plus simplement en demandant au javascript de mettre la valeur du jeton dans les données de publication.

Si vous utilisez {% csrf_token%} dans votre modèle, vous obtiendrez un champ de formulaire masqué émis qui porte la valeur. Mais, si vous utilisez {{csrf_token}}, vous obtiendrez simplement la valeur nue du jeton, vous pouvez donc l'utiliser en javascript comme ceci ...

csrf_token = "{{ csrf_token }}";

Ensuite, vous pouvez inclure cela, avec le nom de clé requis dans le hachage que vous soumettez ensuite en tant que données à l'appel ajax.

fatgeekuk
la source
@aehlke Vous pouvez avoir des fichiers statiques. Dans le code source, vous pouvez voir un bel exemple, où vous enregistrez des variables django dans l' windowobjet, afin qu'elles soient accessibles par la suite. Même dans les fichiers statiques.
KitKat
3
@KitKat en effet :) Désolé pour mon ancien commentaire ignorant ici. Bon point.
aehlke
concernant les fichiers statiques. Pas de problème, si cela ne vous dérange pas un tout petit peu de js votre html. Je viens de mettre {{csrf_token}} dans le template html principal, non loin des incantations requirejs. a fonctionné comme un charme.
JL Peyret
14

Les {% csrf_token %}modèles html mis à l'intérieur<form></form>

se traduit par quelque chose comme:

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

alors pourquoi ne pas simplement le grep dans votre JS comme ceci:

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

puis passez-le, par exemple en effectuant un POST, comme:

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

Réponse 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;
};

usage:

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

la source
8

Si votre formulaire se poste correctement dans Django sans JS, vous devriez pouvoir l'améliorer progressivement avec ajax sans aucun piratage ou passage désordonné du jeton csrf. Sérialisez simplement le formulaire entier et cela récupérera automatiquement tous vos champs de formulaire, y compris le champ csrf caché:

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

J'ai testé cela avec Django 1.3+ et jQuery 1.5+. Évidemment, cela fonctionnera pour n'importe quel formulaire HTML, pas seulement pour les applications Django.

GivP
la source
5

Utilisez Firefox avec Firebug. Ouvrez l'onglet «Console» lors du lancement de la requête ajax. Avec DEBUG=Truevous obtenez la belle page d'erreur django comme réponse et vous pouvez même voir le HTML rendu de la réponse ajax dans l'onglet console.

Ensuite, vous saurez quelle est l'erreur.

jammon
la source
5

La réponse acceptée est probablement un hareng rouge. La différence entre Django 1.2.4 et 1.2.5 était l'exigence d'un jeton CSRF pour les requêtes AJAX.

Je suis tombé sur ce problème sur Django 1.3 et il était dû au fait que le cookie CSRF n'était pas défini en premier lieu. Django ne définira pas le cookie à moins qu'il ne le soit. Ainsi, un site exclusivement ou fortement ajax fonctionnant sous Django 1.2.4 n'aurait potentiellement jamais envoyé de jeton au client et la mise à niveau nécessitant le jeton provoquerait les erreurs 403.

La solution idéale est ici: http://docs.djangoproject.com/en/dev/ref/contrib/csrf/#page-uses-ajax-without-any-html-form
mais vous devrez attendre 1.4 à moins ce n'est que de la documentation rattrapant le code

Éditer

Notez également que la dernière documentation Django note un bogue dans jQuery 1.5, alors assurez-vous que vous utilisez la version 1.5.1 ou ultérieure avec le code suggéré par Django: http://docs.djangoproject.com/en/1.3/ref/contrib/csrf/# Ajax

Steven
la source
Ma réponse était exacte au moment de l'écrire :) C'était juste après que Django ait été mis à jour de 1.2.4 à 1.2.5. C'était aussi quand la dernière version de jQuery était 1.5. Il s'avère que la source du problème était jQuery (1.5) buggée et ces informations sont maintenant ajoutées à Django doc, comme vous l'avez dit. Dans mon cas: le cookie a été défini et le jeton n'a PAS été ajouté à la requête AJAX. Le correctif donné a fonctionné pour jQuery 1.5 bogué. À partir de maintenant, vous pouvez simplement vous en tenir à la documentation officielle, en utilisant l'exemple de code qui y est donné et en utilisant le plus récent jQuery. Votre problème avait une source différente de celle des problèmes discutés ici :)
Jakub Gocławski
2
Il existe maintenant un décorateur appelé ensure_csrf_cookiequi vous permet d'envelopper une vue pour vous assurer qu'il envoie le cookie.
Brian Neal
C'est le problème que j'avais, il n'y a pas de csrftokencookie en premier lieu, merci!
crhodes
5

Il semble que personne n'ait mentionné comment faire cela en pur JS en utilisant l'en- X-CSRFTokentête et {{ csrf_token }}, voici donc une solution simple où vous n'avez pas besoin de rechercher dans les cookies ou le DOM:

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

Comme cela n'est indiqué nulle part dans les réponses actuelles, la solution la plus rapide si vous n'intégrez pas js dans votre modèle est:

Mettez <script type="text/javascript"> window.CSRF_TOKEN = "{{ csrf_token }}"; </script>avant votre référence au fichier script.js dans votre modèle, puis ajoutez csrfmiddlewaretokendans votre datadictionnaire dans votre fichier js:

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

Je viens de rencontrer une situation un peu différente mais similaire. Je ne sais pas à 100% si ce serait une solution à votre cas, mais j'ai résolu le problème pour Django 1.3 en définissant un paramètre POST 'csrfmiddlewaretoken' avec la chaîne de valeur de cookie appropriée qui est généralement renvoyée sous la forme de votre HTML personnel par Django système de modèle avec la balise '{% csrf_token%}'. Je n'ai pas essayé l'ancien Django, juste arrivé et résolu sur Django1.3. Mon problème était que la première demande soumise via Ajax à partir d'un formulaire a été effectuée avec succès, mais la deuxième tentative de la même chose a échoué, a abouti à l'état 403 même si l'en-tête 'X-CSRFToken' est correctement placé avec la valeur du jeton CSRF également comme dans le cas de la première tentative. J'espère que cela t'aides.

Cordialement,

Hiro

Hiroaki Kishimoto
la source
2

vous pouvez coller ce js dans votre fichier html, n'oubliez pas de le mettre avant une autre fonction 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
la source
1

Un jeton CSRF est attribué à chaque session (c'est-à-dire à chaque fois que vous vous connectez). Donc, avant de souhaiter obtenir des données saisies par l'utilisateur et de les envoyer en tant qu'appel ajax à une fonction protégée par le décorateur csrf_protect, essayez de trouver les fonctions qui sont appelées avant d'obtenir ces données de l'utilisateur. Par exemple, un modèle doit être rendu sur lequel votre utilisateur entre des données. Ce modèle est rendu par une fonction. Dans cette fonction, vous pouvez obtenir le jeton csrf comme suit: csrf = request.COOKIES ['csrftoken'] Passez maintenant cette valeur csrf dans le dictionnaire contextuel par rapport au modèle en question. Maintenant, dans ce modèle, écrivez cette ligne: Maintenant, dans votre fonction javascript, avant de faire une requête ajax, écrivez ceci: var csrf = $ ('# csrf'). val () cela choisira la valeur du jeton passé au modèle et la stockera dans la variable csrf. Maintenant, lors de l'appel ajax, dans vos données de publication, transmettez également cette valeur: "csrfmiddlewaretoken": csrf

Cela fonctionnera même si vous n'implémentez pas de formulaires django.

En fait, la logique ici est la suivante: vous avez besoin d'un jeton que vous pouvez obtenir à partir de la demande. Il vous suffit donc de déterminer la fonction appelée immédiatement après la connexion. Une fois que vous avez ce jeton, effectuez un autre appel ajax pour l'obtenir ou passez-le à un modèle accessible par votre ajax.

AMIT PRAKASH PANDEY
la source
Pas très bien structuré, mais bien expliqué. Mon problème était que j'envoyais csrf de cette manière:, csrftoken: csrftokenplutôt que csrfmiddlwaretoken: csrftoken. Après le changement, cela a fonctionné. Merci
presque un débutant
1

pour quelqu'un qui rencontre ceci et essaie de déboguer:

1) le test django csrf (en supposant que vous en envoyez un) est ici

2) Dans mon cas, settings.CSRF_HEADER_NAMEétait défini sur «HTTP_X_CSRFTOKEN» et mon appel AJAX envoyait un en-tête nommé «HTTP_X_CSRF_TOKEN», donc les choses ne fonctionnaient pas. Je pourrais le changer dans l'appel AJAX ou dans le paramètre django.

3) Si vous choisissez de le changer côté serveur, trouvez votre emplacement d'installation de django et lancez un point d'arrêt dans le csrf middleware.f que vous utilisez virtualenv, ce sera quelque chose comme:~/.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, '')

Ensuite, assurez-vous que le csrfjeton provient correctement de la demande.

4) Si vous avez besoin de changer votre en-tête, etc. - modifiez cette variable dans votre fichier de paramètres

daino3
la source
0

Dans mon cas, le problème était avec la configuration nginx que j'ai copiée du serveur principal vers un serveur temporaire avec la désactivation de https qui n'est pas nécessaire sur le second dans le processus.

J'ai dû commenter ces deux lignes dans la configuration pour que cela fonctionne à nouveau:

# uwsgi_param             UWSGI_SCHEME    https;
# uwsgi_pass_header       X_FORWARDED_PROTO;
int_ua
la source
0

Voici une solution moins verbeuse fournie par 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>

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

Braden Holt
la source