Verificação do Django CSRF falhando com uma solicitação Ajax POST

180

Eu poderia usar alguma ajuda em conformidade com o mecanismo de proteção CSRF do Django através do meu post AJAX. Eu segui as instruções aqui:

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

Copiei exatamente o código de exemplo AJAX que eles têm nessa página:

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

Coloquei um alerta imprimindo o conteúdo getCookie('csrftoken')antes da xhr.setRequestHeaderchamada e ele é realmente preenchido com alguns dados. Não sei como verificar se o token está correto, mas sou encorajado por encontrar e enviar algo.

Mas o Django ainda está rejeitando meu post no AJAX.

Aqui está o meu JavaScript:

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

Aqui está o erro que estou vendo no Django:

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

Tenho certeza de que estou perdendo alguma coisa, e talvez seja simples, mas não sei o que é. Eu pesquisei no SO e vi algumas informações sobre como desativar a verificação do CSRF para a minha visualização pelo csrf_exemptdecorador, mas acho isso desagradável. Eu tentei isso e funciona, mas eu prefiro que meu POST funcione da maneira que o Django foi projetado para esperar, se possível.

Caso isso seja útil, aqui está a essência do que minha visão está fazendo:

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

Obrigado por suas respostas!

firebush
fonte
1
Qual versão do django você está usando?
zsquare 24/02
Você adicionou as classes de middleware CSRF corretas e as colocou na ordem correta?
24711 darren
Jakub respondeu minha pergunta abaixo, mas apenas para o caso de ser útil para outras pessoas: @zsquare: versão 1.2.3. @mongoose_za: Sim, eles foram adicionados e na ordem certa.
firebush

Respostas:

181

Solução real

Ok, eu consegui rastrear o problema. Está no código Javascript (como sugeri abaixo).

O que você precisa é este:

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

em vez do código publicado nos documentos oficiais: https://docs.djangoproject.com/en/2.2/ref/csrf/

O código de trabalho vem desta entrada do Django: http://www.djangoproject.com/weblog/2011/feb/08/security/

Portanto, a solução geral é: "use o manipulador ajaxSetup em vez do manipulador ajaxSend". Não sei por que isso funciona. Mas funciona para mim :)

Post anterior (sem resposta)

Estou enfrentando o mesmo problema, na verdade.

Ocorre após a atualização para o Django 1.2.5 - não houve erros nas solicitações de AJAX POST no Django 1.2.4 (o AJAX não estava protegido de forma alguma, mas funcionou bem).

Assim como o OP, tentei o snippet JavaScript publicado na documentação do Django. Estou usando o jQuery 1.5. Também estou usando o middleware "django.middleware.csrf.CsrfViewMiddleware".

Tentei seguir o código do middleware e sei que ele falha com isso:

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

e depois

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

este "se" é verdadeiro, porque "request_csrf_token" está vazio.

Basicamente, isso significa que o cabeçalho NÃO está definido. Então, há algo errado com esta linha JS:

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

?

Espero que os detalhes fornecidos nos ajudem a resolver o problema :)

Jakub Gocławski
fonte
Isso funcionou! Coloquei a função .ajaxSetup como você a colou acima e agora posso postar sem um erro 403. Obrigado por compartilhar a solução, Jakub. Boa descoberta. :)
firebush
Usando ajaxSetupem vez de ajaxSendvai contra os docs jQuery: api.jquery.com/jQuery.ajaxSetup
Mark Lavin
usando 1.3, a entrada oficial da documentação do django funcionou para mim.
monkut
1
Eu tentei, mas isso não parece trabalhar para mim, eu estou usando jQuery v1.7.2, a minha pergunta é stackoverflow.com/questions/11812694/...
daydreamer
Tenho que adicionar a anotação @ensure_csrf_cookie à minha função de exibição para forçar o conjunto de cookies csrf quando a página é solicitada a partir de dispositivos móveis.
Kane
172

Se você usar a $.ajaxfunção, poderá simplesmente adicionar o csrftoken no corpo dos dados:

$.ajax({
    data: {
        somedata: 'somedata',
        moredata: 'moredata',
        csrfmiddlewaretoken: '{{ csrf_token }}'
    },
Bryan
fonte
2
quando uso a resposta marcada, funciona para mim, mas se eu usar sua solução aqui, não funciona. Mas sua solução deve funcionar, não entendo por que não. Há mais alguma coisa que precisa ser feita no Django 1.4?
Houman 26/05/12
1
Obrigado! tão simples. Ainda funciona em Django 1.8 e jQuery 2.1.3
Alejandro Veintimilla
19
Esta solução requer que o javascript seja incorporado no modelo, não é?
Mox
15
@Mox: Colocar isso em html, mas acima de seu arquivo Js, onde é uma função ajax <script type="text/javascript"> window.CSRF_TOKEN = "{{ csrf_token }}"; </script>
HereHere
Obrigado! Tão simples e elegante. Funciona para mim com o Django 1.8. Eu adicionei csrfmiddlewaretoken: '{{ csrf_token }}'ao meu datadicionário em uma $.postchamada.
Pavel Yudaev
75

Adicione esta linha ao seu código jQuery:

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

E feito.

Kambiz
fonte
Eu tentei isso, exceto que meu formulário tem um upload de arquivo. Meu back-end é django e ainda recebo o erro 400CSRF Failed: CSRF token missing or incorrect.
Hussain
16

O problema é que o django espera que o valor do cookie seja devolvido como parte dos dados do formulário. O código da resposta anterior está recebendo o javascript para procurar o valor do cookie e colocá-lo nos dados do formulário. Essa é uma maneira adorável de fazer isso do ponto de vista técnico, mas parece um pouco detalhado.

No passado, eu fiz isso de maneira mais simples, obtendo o javascript para colocar o valor do token nos dados da postagem.

Se você usar {% csrf_token%} em seu modelo, receberá um campo de formulário oculto emitido que carrega o valor. Mas, se você usar o {{csrf_token}}, obterá apenas o valor básico do token, para poder usá-lo em javascript como este ....

csrf_token = "{{ csrf_token }}";

Em seguida, você pode incluir isso, com o nome da chave necessário no hash e depois enviar os dados para a chamada ajax.

fatgeekuk
fonte
@aehlke Você pode ter arquivos estáticos. No código fonte, você pode ver um bom exemplo, onde você registra variáveis ​​django no windowobjeto, para que elas sejam acessíveis posteriormente. Mesmo em arquivos estáticos.
precisa
3
@KitKat de fato :) Desculpe pelo meu comentário antigo e ignorante aqui. Bom ponto.
aehlke
arquivos estáticos. Não é um problema, se você não se importa com um pouco de js seu html. Acabei de colocar {{csrf_token}} no modelo principal de html, não muito longe dos encantamentos requirejs. funcionou como um encanto.
JL Peyret #
14

O {% csrf_token %}colocar em modelos html dentro<form></form>

traduz para algo como:

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

então por que não apenas cumprimentá-lo em seu JS assim:

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

e depois passar, por exemplo, fazendo algum POST, como:

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

Resposta não-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);

fonte
8

Se o seu formulário for postado corretamente no Django sem JS, você poderá aprimorá-lo progressivamente com ajax sem nenhuma invasão ou passagem desorganizada do token csrf. Basta serializar o formulário inteiro e isso selecionará automaticamente todos os campos do formulário, incluindo o campo csrf oculto:

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

Eu testei isso com o Django 1.3+ e o jQuery 1.5+. Obviamente, isso funcionará para qualquer formulário HTML, não apenas para aplicativos Django.

GivP
fonte
5

Use o Firefox com Firebug. Abra a guia 'Console' enquanto dispara a solicitação ajax. Com DEBUG=Truevocê obtém a boa página de erro do django como resposta e você pode até ver o html renderizado da resposta do ajax na guia console.

Então você saberá qual é o erro.

jammon
fonte
5

A resposta aceita é provavelmente um arenque vermelho. A diferença entre o Django 1.2.4 e 1.2.5 foi o requisito para um token CSRF para solicitações AJAX.

Me deparei com esse problema no Django 1.3 e foi causado pelo cookie CSRF não estar definido em primeiro lugar. O Django não definirá o cookie, a menos que seja necessário. Portanto, um site exclusivo ou fortemente ajax em execução no Django 1.2.4 nunca teria potencialmente enviado um token para o cliente e a atualização que requer o token causaria os erros 403.

A correção ideal está aqui: http://docs.djangoproject.com/en/dev/ref/contrib/csrf/#page-uses-ajax-without-any-html-form,
mas você teria que esperar pelo 1.4, a menos que isso é apenas documentação que acompanha o código

Editar

Observe também que os documentos posteriores do Django observam um bug no jQuery 1.5, portanto, verifique se você está usando a versão 1.5.1 ou posterior com o código sugerido pelo Django: http://docs.djangoproject.com/en/1.3/ref/contrib/csrf/# ajax

Steven
fonte
Minha resposta foi precisa no momento em que a escrevi :) Foi logo após o Django ter sido atualizado de 1.2.4 para 1.2.5. Foi também quando a versão mais recente do jQuery foi 1.5. Acontece que a origem do problema foi corrigida pelo jQuery (1.5) e essas informações foram adicionadas ao doc do Django, como você afirmou. No meu caso: o cookie foi definido e o token NÃO foi adicionado à solicitação AJAX. A correção fornecida funcionou para o jQuery 1.5 com erros. A partir de agora, você pode simplesmente seguir os documentos oficiais, usando o código de exemplo fornecido lá e o jQuery mais recente. Seu problema teve origem diferente do que os problemas discutidos aqui :)
Jakub Gocławski
2
Agora existe um decorador chamado ensure_csrf_cookieque você pode agrupar uma exibição para garantir que ela envie o cookie.
Brian Neal
Esse é o problema que eu estava tendo, não há csrftokencookie em primeiro lugar, obrigado!
Crhodes #
5

Parece que ninguém mencionou como fazer isso em JS puro usando o X-CSRFTokencabeçalho e {{ csrf_token }}, então, aqui está uma solução simples em que você não precisa pesquisar nos cookies ou no DOM:

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

Como não está indicado em nenhum lugar nas respostas atuais, a solução mais rápida se você não estiver incorporando js ao seu modelo é:

Coloque <script type="text/javascript"> window.CSRF_TOKEN = "{{ csrf_token }}"; </script>antes da sua referência ao arquivo script.js no seu modelo e adicione csrfmiddlewaretokenao seu datadicionário no seu arquivo js:

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

Acabei de encontrar uma situação um pouco diferente, mas semelhante. Não tenho 100% de certeza se isso seria uma solução para o seu caso, mas resolvi o problema para o Django 1.3 definindo um parâmetro POST 'csrfmiddlewaretoken' com a string de valor de cookie adequada, que geralmente é retornada no formato HTML doméstico pelo Django sistema de modelos com a tag '{% csrf_token%}'. Eu não experimentei o Django mais antigo, apenas aconteceu e resolvi no Django1.3. Meu problema foi que a primeira solicitação enviada via Ajax a partir de um formulário foi concluída com êxito, mas a segunda tentativa exata do mesmo falhou, resultou no estado 403, mesmo que o cabeçalho 'X-CSRFToken' seja corretamente colocado com o valor do token CSRF como no caso da primeira tentativa. Espero que isto ajude.

Saudações,

Hiro

Hiroaki Kishimoto
fonte
2

você pode colar esse js no seu arquivo html, lembre-se de colocá-lo antes de outras funções do 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
fonte
1

Um token CSRF é atribuído a cada sessão (ou seja, toda vez que você efetua login). Portanto, antes de desejar obter alguns dados inseridos pelo usuário e enviá-los como chamada ajax para alguma função protegida pelo decorador csrf_protect, tente encontrar as funções que estão sendo chamadas antes de obter esses dados do usuário. Por exemplo, algum modelo deve estar sendo renderizado no qual o usuário está inserindo dados. Esse modelo está sendo renderizado por alguma função. Nesta função, você pode obter o token csrf da seguinte maneira: csrf = request.COOKIES ['csrftoken'] Agora, passe esse valor de csrf no dicionário de contexto para o qual o modelo em questão está sendo renderizado. Agora, nesse modelo, escreva esta linha: Agora, na sua função javascript, antes de fazer o pedido ajax, escreva o seguinte: var csrf = $ ('# csrf'). val () seleciona o valor do token passado para o modelo e o armazena na variável csrf. Agora, ao fazer uma chamada ajax, em seus dados de postagem, passe esse valor também: "csrfmiddlewaretoken": csrf

Isso funcionará mesmo se você não estiver implementando formulários django.

De fato, a lógica aqui é: Você precisa de um token que pode ser obtido mediante solicitação. Portanto, você só precisa descobrir a função que está sendo chamada imediatamente após o login. Depois de obter esse token, faça outra chamada ajax para obtê-lo ou passe-o para algum modelo acessível pelo seu ajax.

AMIT PRAKASH PANDEY
fonte
Não muito bem estruturado, mas bem explicado. Meu problema foi que eu estava enviando csrf desta maneira:, em csrftoken: csrftokenvez de csrfmiddlwaretoken: csrftoken. Após a mudança, funcionou. Graças
quase um iniciante
1

para alguém que se depara com isso e está tentando depurar:

1) a verificação do django csrf (assumindo que você está enviando uma) está aqui

2) No meu caso, settings.CSRF_HEADER_NAMEfoi definido como 'HTTP_X_CSRFTOKEN' e minha chamada AJAX estava enviando um cabeçalho chamado 'HTTP_X_CSRF_TOKEN' para que as coisas não funcionassem. Eu poderia alterá-lo na chamada AJAX ou na configuração do django.

3) Se você optar por alterá-lo no lado do servidor, encontre o local de instalação do django e instale um ponto de interrupção no csrf middlewarearquivo .f que estiver usando virtualenv, será algo 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, '')

Em seguida, verifique se o csrftoken foi originado corretamente de request.META

4) Se você precisar alterar seu cabeçalho, etc - altere essa variável no seu arquivo de configurações

daino3
fonte
0

No meu caso, o problema foi com a configuração do nginx que copiei do servidor principal para um temporário com a desativação do https que não é necessário no segundo no processo.

Eu tive que comentar essas duas linhas na configuração para fazê-lo funcionar novamente:

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

Aqui está uma solução menos detalhada fornecida pelo 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/

Braden Holt
fonte