web-dev-qa-db-fra.com

Vérification Django CSRF échouant avec un Ajax POST demande

Je pourrais utiliser un peu d'aide pour respecter le mécanisme de protection CSRF de Django via mon message AJAX. J'ai suivi les instructions ici:

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

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

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

Je mets une alerte imprimant le contenu de getCookie('csrftoken') avant l'appel xhr.setRequestHeader et il est en effet rempli de certaines données. Je ne sais pas comment vérifier que le jeton est correct, mais je suis encouragé de trouver et d'envoyer 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évr. 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 de la fonction CSRF via le décorateur csrf_exempt, mais je trouve cela peu attrayant. J'ai essayé cela et ça marche, mais je préférerais que mon POST fonctionne de la manière dont Django a été conçu pour s'y attendre, si possible.

Juste au cas où cela serait utile, voici l'essentiel de ce que je pense faire:

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!

158
firebush

Solution réelle

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

Ce dont vous avez besoin est ceci:

$.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: http://docs.djangoproject.com/fr/1.2/ref/contrib/csrf/#ajax

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: "utilise 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 a pas eu d'erreur avec les requêtes AJAX POST dans Django 1.2.4 (AJAX n'était protégé d'aucune façon, mais cela fonctionnait parfaitement).

Tout comme OP, j'ai essayé l'extrait de code JavaScript publié dans la documentation de 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 qu'il échoue sur ceci:

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

et alors

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. Donc, y a-t-il un problème 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 :)

162
Jakub Gocławski

Si vous utilisez la fonction $.ajax, vous pouvez simplement ajouter le jeton csrf dans le corps de données:

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

Ajoutez cette ligne à votre code jQuery:

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

et fait.

70
Kambiz

Le problème vient du fait que Django s'attend à ce que la valeur du cookie soit renvoyée dans les données de 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 c’est un peu verbeux.

Dans le passé, je l'ai fait plus simplement en faisant en sorte que le javascript mette 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é contenant la valeur. Mais, si vous utilisez {{csrf_token}}, vous obtiendrez simplement la valeur nue du jeton. Vous pourrez ainsi utiliser ceci en javascript ....

csrf_token = "{{ csrf_token }}";

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

14
fatgeekuk

Le {% csrf_token %} mis dans des modèles HTML à l'intérieur de <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 transmettez-le, par exemple, en faisant un post, comme:

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

Si votre formulaire est correctement posté dans Django sans JS, vous devriez pouvoir l’améliorer progressivement avec ajax sans aucun piratage ni passage en désordre du jeton csrf. Il suffit de sérialiser le formulaire entier et cela ramassera 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.

8
GivP

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

La réponse acceptée est très probablement un hareng rouge. La différence entre Django 1.2.4 et 1.2.5 était la nécessité d'un jeton CSRF pour les demandes AJAX.

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

La solution idéale est la suivante: http://docs.djangoproject.com/en/dev/ref/contrib/csrf/#page-uses-ajax-without-any-html-form
mais vous devrez attendre la version 1.4 sauf s'il s'agit simplement d'une documentation rattrapant le code

Modifier

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

5
Steven

Utilisez Firefox avec Firebug. Ouvrez l'onglet "Console" lors du lancement de la demande ajax. Avec DEBUG=True, vous obtenez la réponse à la page d'erreur Nice Django et vous pouvez même voir le code HTML de la réponse ajax dans l'onglet Console.

Ensuite, vous saurez quelle est l'erreur.

4
jammon

vous pouvez coller ce js dans votre fichier html, n'oubliez pas de le mettre avant une autre fonction

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

2
nooobpan

Il semble que personne n'ait dit comment faire cela dans JS pur en utilisant les en-têtes X-CSRFToken et {{ csrf_token }}. Voici donc une solution simple qui vous évite de chercher dans les cookies ou le DOM:

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

Je viens de rencontrer une situation un peu différente mais similaire. Pas tout à fait sûr que ce soit une solution à votre cas, mais j’ai résolu le problème de 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 message. home HTML par le système de templates de Django avec la balise '{% csrf_token%}'. Je n’ai pas essayé sur le plus ancien Django, c’est arrivé et résolu sur Django1.3 . Mon problème était que la première demande soumise via Ajax à partir d’un formulaire avait été effectuée avec succès, mais que la deuxième tentative du même résultat avait échoué. 403 état même si l'en-tête 'X-CSRFToken' est correctement placé avec la valeur du jeton CSRF ainsi que dans le cas de la première tentative . J'espère que cela vous aidera.

Cordialement,

Hiro

2

Si quelqu'un essaie de résoudre ce problème avec axios, cela m'a aidé:

import axios from 'axios';

axios.defaults.xsrfCookieName = 'csrftoken'
axios.defaults.xsrfHeaderName = 'X-CSRFToken'

Source: https://cbuelter.wordpress.com/2017/04/10/Django-csrf-with-axios/

1
Mikko Pöri

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

1) le chèque Django csrf (en supposant que vous en envoyiez un) est ici

2) Dans mon cas, settings.CSRF_HEADER_NAME était réglé sur 'HTTP_X_CSRFTOKEN' et mon appel AJAX envoyait un en-tête nommé 'HTTP_X_CSRF_TOKEN', donc tout ne fonctionnait pas. Je pouvais soit le changer dans l'appel AJAX, soit le paramètre Django.

3) Si vous choisissez de le changer côté serveur, trouvez votre emplacement d'installation de Django et jetez un point d'arrêt dans le csrf middleware. Si 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 jeton csrf est correctement généré à partir de request.META

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

1
daino3

Un jeton CSRF est attribué à chaque session (c'est-à-dire chaque fois que vous vous connectez) . Donc, avant de souhaiter saisir certaines données et les envoyer en tant qu'appel ajax à une fonction protégée par csrf_protect decorator, essayez de trouver les fonctions appelées avant que vous obteniez ces données de l'utilisateur. Par exemple. un modèle sur lequel votre utilisateur doit saisir des données doit être restitué ..__ Ce modèle est restitué par une fonction . Dans cette fonction, vous pouvez obtenir le jeton csrf comme suit: csrf = request.COOKIES [' csrftoken '] Maintenant, transmettez cette valeur csrf dans le dictionnaire contextuel en fonction duquel le modèle en question est affiché . Maintenant, dans ce modèle, écrivez cette ligne: Maintenant, dans votre fonction javascript, écrivez ceci: : var csrf = $ ('# csrf'). val () cela va choisir la valeur du jeton passé au template et le stocker dans la variable csrf . Maintenant, tout en appelant ajax, dans vos données de post, passez également cette valeur: "csrfmiddlewaretoken": csrf 

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

En fait, la logique est la suivante: vous avez besoin d’un jeton que vous pouvez obtenir à partir de la requête . Il vous suffit donc de déterminer la fonction appelée immédiatement après la connexion. Une fois que vous avez ce jeton, faites un autre appel ajax pour obtenir ou le passer à un modèle accessible par votre ajax.

1

Comme cela n’est indiqué nulle part dans les réponses actuelles, la solution la plus rapide si vous êtes ne pas incorporer js dans votre modèle est la suivante: 

Placez <script type="text/javascript"> window.CSRF_TOKEN = "{{ csrf_token }}"; </script> devant votre référence au fichier script.js dans votre modèle, puis ajoutez csrfmiddlewaretoken dans votre dictionnaire data dans votre fichier js:

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

Voici une solution moins détaillée 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/fr/1.11/ref/csrf/

0
Braden Holt

Dans mon cas, le problème provenait de 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 processus.

Je devais commenter ces deux lignes dans la configuration pour que cela fonctionne à nouveau:

# uwsgi_param             UWSGI_SCHEME    https;
# uwsgi_pass_header       X_FORWARDED_PROTO;
0
int_ua