Django gagal cek CSRF dengan permintaan POST Ajax

180

Saya dapat menggunakan beberapa bantuan yang sesuai dengan mekanisme perlindungan CSRF Django melalui pos AJAX saya. Saya telah mengikuti petunjuk di sini:

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

Saya telah menyalin kode sampel AJAX yang mereka miliki di halaman itu dengan tepat:

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

Saya memasang peringatan mencetak konten getCookie('csrftoken')sebelum xhr.setRequestHeaderpanggilan dan memang diisi dengan beberapa data. Saya tidak yakin bagaimana memverifikasi bahwa token itu benar, tetapi saya didorong bahwa itu menemukan dan mengirim sesuatu.

Tapi Django masih menolak posting AJAX saya.

Ini JavaScript saya:

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

Inilah kesalahan yang saya lihat dari Django:

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

Saya yakin saya kehilangan sesuatu, dan mungkin itu sederhana, tetapi saya tidak tahu apa itu. Saya telah mencari di sekitar SO dan melihat beberapa informasi tentang mematikan cek CSRF untuk pandangan saya melaluicsrf_exempt dekorator, tetapi saya menemukan itu tidak menarik. Saya sudah mencobanya dan berhasil, tetapi saya lebih suka mendapatkan POST saya untuk bekerja dengan cara yang dirancang Django untuk mengharapkannya, jika memungkinkan.

Untuk berjaga-jaga jika ini membantu, inilah intisari dari pandangan saya:

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

Terima kasih atas balasan Anda!

Firebush
sumber
1
Django versi apa yang Anda gunakan?
zsquare
Sudahkah Anda menambahkan di kelas middleware CSRF yang benar dan menempatkannya dalam urutan yang benar?
darren
Jakub menjawab pertanyaan saya di bawah, tapi kalau-kalau itu berguna untuk orang lain: @zsquare: versi 1.2.3. @mongoose_za: Ya, mereka ditambahkan dan dalam urutan yang benar.
firebush

Jawaban:

181

Solusi nyata

Oke, saya berhasil melacak masalahnya. Itu terletak pada kode Javascript (seperti yang saya sarankan di bawah).

Yang Anda butuhkan adalah ini:

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

alih-alih kode yang diposting di dokumen resmi: https://docs.djangoproject.com/en/2.2/ref/csrf/

Kode yang berfungsi, berasal dari entri Django ini: http://www.djangoproject.com/weblog/2011/feb/08/security/

Jadi solusi umumnya adalah: "gunakan handler ajaxSetup bukan ajaxSend handler". Saya tidak tahu mengapa itu berhasil. Tapi hal ini bekerja untukku :)

Posting sebelumnya (tanpa jawaban)

Sebenarnya saya mengalami masalah yang sama.

Itu terjadi setelah memperbarui ke Django 1.2.5 - tidak ada kesalahan dengan permintaan POST AJAX di Django 1.2.4 (AJAX tidak dilindungi dengan cara apa pun, tetapi bekerja dengan baik).

Sama seperti OP, saya telah mencoba cuplikan JavaScript yang diposting di dokumentasi Django. Saya menggunakan jQuery 1.5. Saya juga menggunakan middleware "django.middleware.csrf.CsrfViewMiddleware".

Saya mencoba mengikuti kode middleware dan saya tahu itu gagal pada ini:

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

lalu

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

ini "jika" benar, karena "request_csrf_token" kosong.

Pada dasarnya itu berarti bahwa header TIDAK diatur. Jadi apakah ada yang salah dengan garis JS ini:

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

?

Saya harap detail yang disediakan akan membantu kami menyelesaikan masalah :)

Jakub Gocławski
sumber
Ini Berhasil! Saya memasukkan fungsi .ajaxSetup saat Anda menempelkannya di atas dan sekarang saya dapat memposting tanpa kesalahan 403. Terima kasih telah berbagi solusinya, Jakub. Bagus temukan. :)
firebush
Menggunakan ajaxSetupdaripada ajaxSendmenjalankan counter ke dokumen jQuery: api.jquery.com/jQuery.ajaxSetup
Mark Lavin
menggunakan 1.3, entri dokumentasi Django resmi bekerja untuk saya.
monkut
1
Saya mencoba tetapi ini sepertinya tidak berhasil untuk saya, saya menggunakan jQuery v1.7.2, pertanyaan saya adalah stackoverflow.com/questions/11812694/…
daydreamer
Saya harus menambahkan anotasi @ensure_csrf_cookie ke fungsi tampilan saya untuk memaksa mengatur cookie csrf ketika halaman diminta dari perangkat seluler.
Kane
172

Jika Anda menggunakan $.ajaxfungsi ini, Anda bisa menambahkan csrftoken di badan data:

$.ajax({
    data: {
        somedata: 'somedata',
        moredata: 'moredata',
        csrfmiddlewaretoken: '{{ csrf_token }}'
    },
Bryan
sumber
2
ketika saya menggunakan jawaban yang ditandai itu berfungsi untuk saya, tetapi jika saya menggunakan solusi Anda di sini tidak. Tetapi solusi Anda harus berhasil, saya tidak mengerti mengapa tidak. Apakah ada hal lain yang perlu dilakukan di Django 1.4?
Houman
1
Terima kasih! sangat sederhana. Masih bekerja pada Django 1.8 dan jquery 2.1.3
Alejandro Veintimilla
19
Solusi ini membutuhkan javascript yang akan tertanam dalam template bukan?
Mox
15
@Mox: Masukkan ini dalam html, tetapi di atas file Js Anda di mana adalah fungsi ajax <script type="text/javascript"> window.CSRF_TOKEN = "{{ csrf_token }}"; </script>
HereHere
Terima kasih! Sangat sederhana dan elegan. Ini bekerja untuk saya dengan Django 1.8. Saya menambahkan csrfmiddlewaretoken: '{{ csrf_token }}'ke datakamus saya melalui $.posttelepon.
Pavel Yudaev
75

Tambahkan baris ini ke kode jQuery Anda:

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

dan dilakukan.

Kambiz
sumber
Saya mencoba ini, kecuali formulir saya memiliki unggahan file. Backend saya adalah Django dan masih mendapatkan error 400CSRF Failed: CSRF token missing or incorrect.
Hussain
16

Masalahnya adalah karena Django mengharapkan nilai dari cookie akan dikembalikan sebagai bagian dari data formulir. Kode dari jawaban sebelumnya adalah mendapatkan javascript untuk mencari nilai cookie dan memasukkannya ke dalam formulir data. Itu cara yang indah untuk melakukannya dari sudut pandang teknis, tetapi memang terlihat sedikit verbose.

Di masa lalu, saya telah melakukannya lebih sederhana dengan mendapatkan javascript untuk memasukkan nilai token ke dalam data posting.

Jika Anda menggunakan {% csrf_token%} di templat Anda, Anda akan mendapatkan bidang formulir tersembunyi yang dipancarkan yang membawa nilainya. Tetapi, jika Anda menggunakan {{csrf_token}} Anda hanya akan mendapatkan nilai telanjang token, sehingga Anda dapat menggunakan ini dalam javascript seperti ini ....

csrf_token = "{{ csrf_token }}";

Kemudian Anda bisa memasukkan itu, dengan nama kunci yang diperlukan dalam hash Anda kemudian kirimkan sebagai data ke panggilan ajax.

fatgeekuk
sumber
@ aehlke Anda dapat memiliki file statis. Dalam kode sumber, Anda dapat melihat contoh yang bagus, di mana Anda mendaftarkan variabel django di windowobjek, sehingga mereka dapat diakses sesudahnya. Bahkan dalam file statis.
KitKat
3
@KitKat memang :) Maaf atas komentar saya yang kuno dan bodoh di sini. Poin yang bagus.
aehlke
kembali file statis. Bukan masalah, jika Anda tidak keberatan sedikit js html Anda. Saya hanya memasukkan {{csrf_token}} di templat html utama, tidak jauh dari mantra needsejs. bekerja seperti pesona.
JL Peyret
14

The {% csrf_token %}put di html template dalam<form></form>

diterjemahkan menjadi sesuatu seperti:

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

jadi mengapa tidak hanya mengambilnya di JS Anda seperti ini:

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

dan kemudian meneruskannya mis melakukan POST, seperti:

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

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

pemakaian:

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

sumber
8

Jika form Anda memposting dengan benar di Django tanpa JS, Anda harus dapat meningkatkannya secara progresif dengan ajax tanpa ada peretasan atau pengalihan csrf token yang berantakan. Serialkan seluruh formulir dan itu akan secara otomatis mengambil semua bidang formulir Anda termasuk bidang csrf tersembunyi:

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

Saya sudah menguji ini dengan Django 1.3+ dan jQuery 1.5+. Jelas ini akan bekerja untuk semua bentuk HTML, bukan hanya aplikasi Django.

GivP
sumber
5

Gunakan Firefox dengan Firebug. Buka tab 'Konsol' sambil menjalankan permintaan ajax. DenganDEBUG=True Anda mendapatkan halaman kesalahan django yang bagus sebagai respons dan Anda bahkan dapat melihat html yang diberikan dari respons ajax di tab konsol.

Maka Anda akan tahu apa kesalahannya.

jammon
sumber
5

Jawaban yang diterima kemungkinan besar adalah herring merah. Perbedaan antara Django 1.2.4 dan 1.2.5 adalah persyaratan untuk token CSRF untuk permintaan AJAX.

Saya menemukan masalah ini pada Django 1.3 dan itu disebabkan oleh cookie CSRF yang tidak disetel di tempat pertama. Django tidak akan mengatur cookie kecuali jika harus. Jadi situs eksklusif atau sangat ajax yang berjalan pada Django 1.2.4 berpotensi tidak akan pernah mengirim token ke klien dan kemudian peningkatan yang memerlukan token akan menyebabkan 403 kesalahan.

Perbaikan yang ideal ada di sini: http://docs.djangoproject.com/en/dev/ref/contrib/csrf/#page-uses-ajax-without-any-html-form
tetapi Anda harus menunggu 1,4 kecuali ini hanya dokumentasi yang mengejar kode

Edit

Perhatikan juga bahwa dokumen Django nanti mencatat bug di jQuery 1.5 jadi pastikan Anda menggunakan 1.5.1 atau lebih baru dengan kode yang disarankan Django: http://docs.djangoproject.com/en/1.3/ref/contrib/csrf/# ajax

Steven
sumber
Jawaban saya akurat pada saat menulisnya :) Itu hanya setelah Django diperbarui dari 1.2.4 ke 1.2.5. Itu juga ketika versi jQuery terbaru 1,5. Ternyata sumber masalahnya adalah disadap jQuery (1.5) dan informasi ini sekarang ditambahkan ke Django doc, seperti yang telah Anda nyatakan. Dalam kasus saya: cookie diset dan token TIDAK ditambahkan ke permintaan AJAX. Perbaikan yang diberikan bekerja untuk jQuery 1.5 yang disadap. Sampai sekarang, Anda dapat tetap berpegang pada dokumen resmi, menggunakan kode contoh yang diberikan di sana dan menggunakan jQuery terbaru. Masalah Anda memiliki sumber yang berbeda dari masalah yang dibahas di sini :)
Jakub Gocławski
2
Sekarang ada dekorator yang disebut ensure_csrf_cookiebahwa Anda dapat membungkus tampilan untuk memastikan mengirimkan cookie.
Brian Neal
Ini adalah masalah yang saya alami, tidak ada csrftokencookie di tempat pertama, terima kasih!
Crhodes
5

Sepertinya tidak ada yang menyebutkan cara melakukan ini di JS murni menggunakan X-CSRFTokenheader dan {{ csrf_token }}, jadi inilah solusi sederhana di mana Anda tidak perlu mencari melalui cookie atau DOM:

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

Karena tidak dinyatakan di mana pun dalam jawaban saat ini, solusi tercepat jika Anda tidak menanamkan js ke template Anda adalah:

Masukkan <script type="text/javascript"> window.CSRF_TOKEN = "{{ csrf_token }}"; </script>sebelum referensi Anda ke file script.js di template Anda, lalu tambahkan csrfmiddlewaretokenke datakamus Anda di file js Anda:

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

Saya baru saja mengalami situasi yang sedikit berbeda tetapi serupa. Tidak 100% yakin apakah itu akan menjadi resolusi untuk kasus Anda, tapi saya menyelesaikan masalah untuk Django 1.3 dengan menetapkan parameter POST 'csrfmiddlewaretoken' dengan string nilai cookie yang tepat yang biasanya dikembalikan dalam bentuk HTML rumah Anda oleh Django sistem template dengan tag '{% csrf_token%}'. Saya tidak mencoba pada Django yang lebih lama, hanya terjadi dan diselesaikan pada Django1.3. Masalah saya adalah bahwa permintaan pertama yang diajukan melalui Ajax dari formulir berhasil dilakukan tetapi upaya kedua dari yang sama persis dari gagal, menghasilkan status 403 meskipun tajuk 'X-CSRFToken' ditempatkan dengan benar dengan nilai token CSRF juga seperti dalam kasus upaya pertama. Semoga ini membantu.

Salam,

Hiro

Hiroaki Kishimoto
sumber
2

Anda dapat menempelkan js ini ke file html Anda, ingat taruh sebelum fungsi js lainnya

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

Satu token CSRF ditugaskan untuk setiap sesi (yaitu setiap kali Anda masuk). Jadi sebelum Anda ingin mendapatkan beberapa data yang dimasukkan oleh pengguna dan mengirimkannya sebagai panggilan ajax ke beberapa fungsi yang dilindungi oleh dekorator csrf_protect, cobalah untuk menemukan fungsi yang dipanggil sebelum Anda mendapatkan data ini dari pengguna. Misalnya, beberapa templat harus dirender tempat pengguna Anda memasukkan data. Template itu sedang dibuat oleh beberapa fungsi. Dalam fungsi ini Anda bisa mendapatkan token csrf sebagai berikut: csrf = request.COOKIES ['csrftoken'] Sekarang berikan nilai csrf ini dalam kamus konteks terhadap template yang dimaksud sedang dirender. Sekarang dalam templat itu tulis baris ini: Sekarang dalam fungsi javascript Anda, sebelum membuat permintaan ajax, tulis ini: var csrf = $ ('# csrf'). val () ini akan memilih nilai token yang diteruskan ke templat dan menyimpannya dalam variabel csrf. Sekarang saat melakukan panggilan ajax, dalam data posting Anda, sampaikan nilai ini juga: "csrfmiddlewaretoken": csrf

Ini akan berfungsi bahkan jika Anda tidak menerapkan formulir Django.

Sebenarnya, logika di sini adalah: Anda perlu token yang bisa Anda dapatkan dari permintaan. Jadi, Anda hanya perlu mencari tahu fungsi yang dipanggil segera setelah masuk. Setelah Anda memiliki token ini, buat panggilan ajax lain untuk mendapatkannya atau kirimkan ke beberapa templat yang dapat diakses oleh ajax Anda.

AMIT PRAKASH PANDEY
sumber
Tidak terstruktur dengan sangat baik, tetapi dijelaskan dengan baik. Masalah saya adalah, saya mengirim csrf dengan cara ini:, csrftoken: csrftokendaripada csrfmiddlwaretoken: csrftoken. Setelah perubahan, itu berhasil. Terima kasih
hampir seorang pemula
1

untuk seseorang yang menemukan ini dan sedang mencoba melakukan debug:

1) cek csrf Django (dengan asumsi Anda mengirim satu) ada di sini

2) Dalam kasus saya, settings.CSRF_HEADER_NAME disetel ke 'HTTP_X_CSRFTOKEN' dan panggilan AJAX saya mengirim header bernama 'HTTP_X_CSRF_TOKEN' sehingga semuanya tidak berfungsi. Saya bisa mengubahnya di panggilan AJAX, atau pengaturan Django.

3) Jika Anda memilih untuk mengubahnya di sisi server, cari lokasi pemasangan django dan letakkan breakpoint di csrf middleware.f yang Anda gunakan virtualenv, itu akan menjadi seperti:~/.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, '')

Kemudian, pastikan csrf token diambil dengan benar dari request.META

4) Jika Anda perlu mengubah tajuk, dll - ubah variabel itu di file pengaturan Anda

Daino3
sumber
0

Dalam kasus saya masalahnya adalah dengan konfigurasi nginx yang telah saya salin dari server utama ke yang sementara dengan menonaktifkan https yang tidak diperlukan pada yang kedua dalam proses.

Saya harus mengomentari dua baris ini di konfigurasi untuk membuatnya bekerja lagi:

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

Berikut ini adalah solusi yang kurang tepat yang disediakan oleh 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>

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

Braden Holt
sumber