La comprobación de Django CSRF falla con una solicitud POST de Ajax

180

Podría usar un poco de ayuda para cumplir con el mecanismo de protección CSRF de Django a través de mi publicación AJAX. He seguido las instrucciones aquí:

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

Copié exactamente el código de muestra AJAX que tienen en esa página exactamente:

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

Puse una alerta imprimiendo el contenido de getCookie('csrftoken')antes de la xhr.setRequestHeaderllamada y de hecho está poblada con algunos datos. No estoy seguro de cómo verificar que el token sea correcto, pero me alienta que encuentre y envíe algo.

Pero Django sigue rechazando mi publicación AJAX.

Aquí está mi JavaScript:

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

Aquí está el error que estoy viendo de Django:

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

Estoy seguro de que me falta algo, y tal vez sea simple, pero no sé qué es. He buscado alrededor de SO y vi información sobre cómo desactivar la verificación CSRF para mi vista a través del csrf_exemptdecorador, pero me parece poco atractivo. Lo probé y funciona, pero prefiero que mi POST funcione de la manera en que Django fue diseñado para esperarlo, si es posible.

En caso de que sea útil, aquí está la esencia de lo que está haciendo mi punto de vista:

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

Gracias por tus respuestas!

firebush
fuente
1
¿Qué versión de django estás usando?
zsquare
¿Ha agregado las clases correctas de middleware CSRF y las ha colocado en el orden correcto?
darren
Jakub respondió mi pregunta a continuación, pero en caso de que sea útil para otras personas: @zsquare: versión 1.2.3. @mongoose_za: Sí, se agregan y están en el orden correcto.
firebush

Respuestas:

181

Solución real

Ok, logré rastrear el problema. Se encuentra en el código Javascript (como sugerí a continuación).

Lo que necesitas es esto:

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

en lugar del código publicado en los documentos oficiales: https://docs.djangoproject.com/en/2.2/ref/csrf/

El código de trabajo proviene de esta entrada de Django: http://www.djangoproject.com/weblog/2011/feb/08/security/

Entonces, la solución general es: "usar el controlador ajaxSetup en lugar del controlador ajaxSend". No sé por qué funciona. Pero funciona para mí :)

Publicación anterior (sin respuesta)

Estoy experimentando el mismo problema en realidad.

Ocurre después de actualizar a Django 1.2.5: no hubo errores con las solicitudes POST de AJAX en Django 1.2.4 (AJAX no estaba protegido de ninguna manera, pero funcionó bien).

Al igual que OP, probé el fragmento de JavaScript publicado en la documentación de Django. Estoy usando jQuery 1.5. También estoy usando el middleware "django.middleware.csrf.CsrfViewMiddleware".

Traté de seguir el código de middleware y sé que falla en esto:

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

y entonces

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

este "si" es verdadero, porque "request_csrf_token" está vacío.

Básicamente significa que el encabezado NO está configurado. Entonces, ¿hay algo malo con esta línea JS:

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

?

Espero que los detalles proporcionados nos ayuden a resolver el problema :)

Jakub Gocławski
fuente
Esto funcionó! Puse la función .ajaxSetup cuando la pegó arriba y ahora puedo publicar sin un error 403. Gracias por compartir la solución, Jakub. Buen descubrimiento. :)
firebush
Usar en ajaxSetuplugar de ajaxSendejecutar en contra de los documentos de jQuery: api.jquery.com/jQuery.ajaxSetup
Mark Lavin
usando 1.3, la entrada de documentación oficial de django funcionó para mí.
monkut
1
Lo intenté pero esto no parece funcionar para mí, estoy usando jQuery v1.7.2, mi pregunta es stackoverflow.com/questions/11812694/…
daydreamer
Tengo que agregar una anotación @ensure_csrf_cookie a mi función de vista para forzar la configuración de la cookie csrf cuando la página se solicita desde dispositivos móviles.
Kane
172

Si usa la $.ajaxfunción, simplemente puede agregar el csrftoken en el cuerpo de datos:

$.ajax({
    data: {
        somedata: 'somedata',
        moredata: 'moredata',
        csrfmiddlewaretoken: '{{ csrf_token }}'
    },
Bryan
fuente
2
cuando uso la respuesta marcada, funciona para mí, pero si uso su solución aquí no lo hace. Pero su solución debería funcionar, no entiendo por qué no. ¿Hay algo más que deba hacerse en Django 1.4?
Houman
1
¡Gracias! tan sencillo. Todavía funciona en django 1.8 y jquery 2.1.3
Alejandro Veintimilla
19
Esta solución requiere que JavaScript esté incrustado en la plantilla, ¿no?
Mox
15
@ Mox: Pon esto en html, pero encima de tu archivo Js donde hay una función ajax <script type="text/javascript"> window.CSRF_TOKEN = "{{ csrf_token }}"; </script>
Aquí Aquí
¡Gracias! Tan simple y elegante. A mí me funciona con Django 1.8. Agregué csrfmiddlewaretoken: '{{ csrf_token }}'a mi datadiccionario en una $.postllamada.
Pavel Yudaev
75

Agregue esta línea a su código jQuery:

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

y hecho.

Kambiz
fuente
Intenté esto, excepto que mi formulario tiene un archivo cargado. Mi backend es django y sigo recibiendo el error 400CSRF Failed: CSRF token missing or incorrect.
Hussain
16

El problema se debe a que django espera que el valor de la cookie se transfiera como parte de los datos del formulario. El código de la respuesta anterior está haciendo que JavaScript busque el valor de la cookie y lo coloque en los datos del formulario. Es una forma encantadora de hacerlo desde un punto de vista técnico, pero se ve un poco detallado.

En el pasado, lo hice más simplemente obteniendo el javascript para poner el valor del token en los datos de la publicación.

Si utiliza {% csrf_token%} en su plantilla, obtendrá un campo de formulario oculto que lleva el valor. Pero, si usa {{csrf_token}}, obtendrá el valor básico del token, por lo que puede usarlo en JavaScript de esta manera ...

csrf_token = "{{ csrf_token }}";

Luego puede incluir eso, con el nombre de clave requerido en el hash que luego envía como datos a la llamada ajax.

fatgeekuk
fuente
@aehlke Puede tener archivos estáticos. En el código fuente, puede ver un buen ejemplo, donde registra variables django en el windowobjeto, para que luego sean accesibles. Incluso en archivos estáticos.
KitKat
3
@KitKat en efecto :) Lo siento por mi antiguo e ignorante comentario aquí. Buen punto.
aehlke
re archivos estáticos. No es un problema, si no te importa un poquito de js tu html. Acabo de poner {{csrf_token}} en la plantilla html principal, no muy lejos de los encantamientos requirejs. trabajado como un encanto.
JL Peyret
14

El {% csrf_token %}poner en plantillas html dentro<form></form>

se traduce en algo como:

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

Entonces, ¿por qué no simplemente ponerlo en tu JS así:

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

y luego pasarlo, por ejemplo, haciendo POST, como:

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

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

uso:

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

fuente
8

Si tu formulario se publica correctamente en Django sin JS, deberías poder mejorarlo progresivamente con ajax sin ningún tipo de pirateo o transferencia desordenada del token csrf. Simplemente serialice todo el formulario y eso recogerá automáticamente todos sus campos de formulario, incluido el campo oculto 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;
});

He probado esto con Django 1.3+ y jQuery 1.5+. Obviamente, esto funcionará para cualquier formulario HTML, no solo para las aplicaciones de Django.

GivP
fuente
5

Usa Firefox con Firebug. Abra la pestaña 'Consola' mientras dispara la solicitud ajax. ConDEBUG=True usted obtiene la bonita página de error de django como respuesta e incluso puede ver el html representado de la respuesta ajax en la pestaña de la consola.

Entonces sabrás cuál es el error.

jammon
fuente
5

La respuesta aceptada es muy probablemente un arenque rojo. La diferencia entre Django 1.2.4 y 1.2.5 era el requisito de un token CSRF para solicitudes AJAX.

Encontré este problema en Django 1.3 y fue causado por la cookie CSRF no configurada en primer lugar. Django no configurará la cookie a menos que sea necesario. Por lo tanto, un sitio exclusivo o muy ajax que se ejecute en Django 1.2.4 nunca podría haber enviado un token al cliente y luego la actualización que requiere el token podría causar los errores 403.

La solución ideal está aquí: http://docs.djangoproject.com/en/dev/ref/contrib/csrf/#page-uses-ajax-without-any-html-form
pero tendrías que esperar 1.4 a menos esto es solo documentación que se pone al día con el código

Editar

Tenga en cuenta también que los documentos posteriores de Django notan un error en jQuery 1.5, así que asegúrese de estar utilizando 1.5.1 o posterior con el código sugerido de Django: http://docs.djangoproject.com/en/1.3/ref/contrib/csrf/# ajax

Steven
fuente
Mi respuesta fue precisa al momento de escribirlo :) Fue justo después de que Django se actualizó de 1.2.4 a 1.2.5. También fue cuando la versión más nueva de jQuery fue 1.5. Resulta que la fuente del problema fue jQuery con errores (1.5) y esta información ahora se agrega a Django doc, como usted ha dicho. En mi caso: la cookie se configuró y el token NO se agregó a la solicitud de AJAX. La corrección dada funcionó para jQuery 1.5 con errores. A partir de ahora, simplemente puede apegarse a los documentos oficiales, usando el código de ejemplo que se proporciona allí y el jQuery más nuevo. Su problema tenía una fuente diferente a los problemas discutidos aquí :)
Jakub Gocławski
2
Ahora hay un decorador llamado ensure_csrf_cookieque puede ajustar alrededor de una vista para asegurarse de que envía la cookie.
Brian Neal
Este es el problema que estaba teniendo, no hay csrftokencookies en primer lugar, ¡gracias!
cruza el
5

Parece que nadie ha mencionado cómo hacer esto en JS puro usando el X-CSRFTokenencabezado y {{ csrf_token }}, así que aquí hay una solución simple donde no necesita buscar a través de las cookies o el DOM:

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

Como no se indica en ninguna parte de las respuestas actuales, la solución más rápida si no está incrustando js en su plantilla es:

Ponga <script type="text/javascript"> window.CSRF_TOKEN = "{{ csrf_token }}"; </script>antes de su referencia al archivo script.js en su plantilla, luego agregue csrfmiddlewaretokena su datadiccionario en su archivo js:

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

Acabo de encontrar una situación un poco diferente pero similar. No estoy 100% seguro de si sería una resolución para su caso, pero resolví el problema para Django 1.3 configurando un parámetro POST 'csrfmiddlewaretoken' con la cadena de valor de cookie adecuada que Django devuelve generalmente en la forma de su HTML de inicio. sistema de plantillas con la etiqueta '{% csrf_token%}'. No probé el Django anterior, simplemente sucedió y resolví en Django1.3. Mi problema fue que la primera solicitud enviada a través de Ajax desde un formulario se realizó con éxito, pero el segundo intento exacto del mismo falló, resultó en un estado 403 a pesar de que el encabezado 'X-CSRFToken' también se coloca correctamente con el valor de token CSRF también como en el caso del primer intento. Espero que esto ayude.

Saludos,

Hiro

Hiroaki Kishimoto
fuente
2

puede pegar este js en su archivo html, recuerde ponerlo antes de otra función 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
fuente
1

Se asigna un token CSRF a cada sesión (es decir, cada vez que inicia sesión). Entonces, antes de que desee obtener algunos datos ingresados ​​por el usuario y enviarlos como una llamada ajax a alguna función que esté protegida por el decorador csrf_protect, intente encontrar las funciones que se están llamando antes de obtener estos datos del usuario. Por ejemplo, se debe representar una plantilla en la que su usuario está ingresando datos. Esa plantilla está siendo representada por alguna función. En esta función, puede obtener el token csrf de la siguiente manera: csrf = request.COOKIES ['csrftoken'] Ahora pase este valor csrf en el diccionario de contexto contra el cual se representa la plantilla en cuestión. Ahora en esa plantilla escriba esta línea: Ahora en su función javascript, antes de hacer una solicitud ajax, escriba esto: var csrf = $ ('# csrf'). val () esto elegirá el valor del token pasado a la plantilla y lo almacenará en la variable csrf. Ahora, mientras realiza una llamada ajax, en sus datos de publicación, pase este valor también: "csrfmiddlewaretoken": csrf

Esto funcionará incluso si no está implementando formularios django.

De hecho, la lógica aquí es: necesita un token que puede obtener de la solicitud. Por lo tanto, solo necesita averiguar la función que se llama inmediatamente después de iniciar sesión. Una vez que tenga este token, realice otra llamada ajax para obtenerlo o páselo a alguna plantilla a la que pueda acceder su ajax.

AMIT PRAKASH PANDEY
fuente
No muy bien estructurado, pero bien explicado. Mi problema era que estaba enviando csrf de esta manera: en csrftoken: csrftokenlugar de csrfmiddlwaretoken: csrftoken. Después del cambio, funcionó. Gracias
casi un principiante
1

para alguien que se encuentra con esto y está tratando de depurar:

1) el cheque csrf de django (suponiendo que está enviando uno) está aquí

2) En mi caso, settings.CSRF_HEADER_NAMEestaba configurado en 'HTTP_X_CSRFTOKEN' y mi llamada AJAX estaba enviando un encabezado llamado 'HTTP_X_CSRF_TOKEN', por lo que las cosas no funcionaban. Podría cambiarlo en la llamada AJAX o en la configuración de django.

3) Si opta por cambiarlo en el lado del servidor, busque su ubicación de instalación de django y arroje un punto de interrupción en el csrf middleware.f que está utilizando virtualenv, será algo así como:~/.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, '')

Luego, asegúrese de que el csrftoken se obtenga correctamente de la solicitud.

4) Si necesita cambiar su encabezado, etc., cambie esa variable en su archivo de configuración

daino3
fuente
0

En mi caso, el problema fue con la configuración de nginx que he copiado del servidor principal a uno temporal con la desactivación de https que no es necesaria en el segundo en el proceso.

Tuve que comentar estas dos líneas en la configuración para que funcione nuevamente:

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

Aquí hay una solución menos detallada proporcionada por 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>

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

Braden Holt
fuente