Kiểm tra Django CSRF không thành công với yêu cầu Ajax POST


180

Tôi có thể sử dụng một số trợ giúp tuân thủ cơ chế bảo vệ CSRF của Django thông qua bài đăng AJAX của tôi. Tôi đã làm theo chỉ dẫn ở đây:

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

Tôi đã sao chép chính xác mã mẫu AJAX trên trang đó:

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

Tôi đặt một cảnh báo in nội dung getCookie('csrftoken')trướcxhr.setRequestHeader cuộc gọi và nó thực sự được điền với một số dữ liệu. Tôi không chắc chắn làm thế nào để xác minh rằng mã thông báo là chính xác, nhưng tôi khuyến khích rằng nó tìm và gửi một cái gì đó.

Nhưng Django vẫn từ chối bài AJAX của tôi.

Đây là JavaScript của tôi:

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

Đây là lỗi tôi thấy từ Django:

[23/2/2011 22:08:29] "POST / ghi nhớ / HTTP / 1.1" 403 2332

Tôi chắc chắn rằng tôi đang thiếu một cái gì đó, và có lẽ nó đơn giản, nhưng tôi không biết nó là gì. Tôi đã tìm kiếm xung quanh SO và thấy một số thông tin về việc tắt kiểm tra CSRF cho chế độ xem của tôi thông quacsrf_exempt trang trí, nhưng tôi thấy điều đó không hấp dẫn. Tôi đã thử nó và nó hoạt động, nhưng tôi muốn POST của tôi hoạt động theo cách Django được thiết kế để mong đợi nó, nếu có thể.

Chỉ trong trường hợp nó hữu ích, đây là ý chính của những gì quan điểm của tôi đang làm:

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

Cảm ơn bạn đã trả lời!


1
Bạn đang sử dụng phiên bản nào của django?
zsapes

Bạn đã thêm vào các lớp trung gian CSRF chính xác và sắp xếp chúng theo đúng thứ tự chưa?
darren

Jakub đã trả lời câu hỏi của tôi dưới đây, nhưng chỉ trong trường hợp nó hữu ích với người khác: @zsapes: phiên bản 1.2.3. @mongoose_za: Vâng, chúng được thêm vào và theo đúng thứ tự.
firebush

Câu trả lời:


181

Giải pháp thực sự

Ok, tôi quản lý để theo dõi vấn đề xuống. Nó nằm trong mã Javascript (như tôi đề xuất bên dưới).

Những gì bạn cần là đây:

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

thay vì mã được đăng trong các tài liệu chính thức: https://docs.djangoproject.com/en/2.2/ref/csrf/

Mã làm việc, xuất phát từ mục nhập Django này: http://www.djangoproject.com/weblog/2011/feb/08/security/

Vì vậy, giải pháp chung là: "sử dụng trình xử lý ajaxSetup thay vì trình xử lý ajaxSend". Tôi không biết tại sao nó hoạt động. Nhưng nó làm việc cho tôi :)

Bài đăng trước (không có câu trả lời)

Tôi đang gặp vấn đề tương tự thực sự.

Nó xảy ra sau khi cập nhật lên Django 1.2.5 - không có lỗi với các yêu cầu AJAX POST trong Django 1.2.4 (AJAX không được bảo vệ theo bất kỳ cách nào, nhưng nó hoạt động rất tốt).

Giống như OP, tôi đã thử đoạn mã JavaScript được đăng trong tài liệu Django. Tôi đang sử dụng jQuery 1.5. Tôi cũng đang sử dụng phần mềm trung gian "django.middleware.csrf.CsrfViewMiddleware".

Tôi đã cố làm theo mã phần mềm trung gian và tôi biết rằng nó thất bại về điều này:

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

và sau đó

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

"nếu" này là đúng, vì "request_csrf_token" trống.

Về cơ bản nó có nghĩa là tiêu đề KHÔNG được đặt. Vì vậy, có gì sai với dòng JS này:

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

?

Tôi hy vọng rằng các chi tiết được cung cấp sẽ giúp chúng tôi giải quyết vấn đề :)


Điều này đã làm việc! Tôi đã đặt chức năng .ajaxSetup khi bạn dán nó ở trên và bây giờ tôi có thể đăng bài mà không gặp lỗi 403. Cảm ơn bạn đã chia sẻ giải pháp, Jakub. Tìm tốt :)
firebush

Sử dụng ajaxSetupthay vì ajaxSendchạy ngược lại với các tài liệu jQuery: api.jquery.com/jQuery.ajaxSetup
Mark Lavin

sử dụng 1.3, mục tài liệu django chính thức làm việc cho tôi.
monkut

1
Tôi đã thử nhưng điều này dường như không hiệu quả với tôi, tôi đang sử dụng jQuery v1.7.2, câu hỏi của tôi là stackoverflow.com/questions/11812694/
mơ mộng ngày

Tôi phải thêm chú thích @ensure_csrf_cookie vào chức năng xem của mình để buộc đặt cookie csrf khi trang được yêu cầu từ thiết bị di động.
Kane

172

Nếu bạn sử dụng $.ajaxchức năng, bạn chỉ cần thêm csrfmã thông báo vào phần thân dữ liệu:

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

2
Khi tôi sử dụng câu trả lời được đánh dấu, nó hoạt động với tôi, nhưng nếu tôi sử dụng giải pháp của bạn ở đây thì không. Nhưng giải pháp của bạn sẽ làm việc mặc dù, tôi không hiểu tại sao nó không. Có điều gì khác cần phải được thực hiện trong Django 1.4 không?
Houman

1
Cảm ơn! quá đơn giản. Vẫn hoạt động trên django 1.8 và jquery 2.1.3
Alejandro Veintimilla

19
Giải pháp này yêu cầu javascript được nhúng trong mẫu phải không?
Mox

15
@Mox: Đặt cái này trong html, nhưng phía trên tệp Js của bạn có chức năng ajax <script type="text/javascript"> window.CSRF_TOKEN = "{{ csrf_token }}"; </script>
Đây là

Cảm ơn! Thật đơn giản và thanh lịch. Nó hoạt động với tôi với Django 1.8. Tôi đã thêm vào từ điển csrfmiddlewaretoken: '{{ csrf_token }}'của tôi datatrong một $.postcuộc gọi.
Pavel Yudaev

75

Thêm dòng này vào mã jQuery của bạn:

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

và thực hiện.


Tôi đã thử điều này, ngoại trừ hình thức của tôi có một tập tin tải lên. Phần cuối của tôi là django và vẫn gặp lỗi 400CSRF Failed: CSRF token missing or incorrect.
Hussain

16

Vấn đề là bởi vì django đang mong đợi giá trị từ cookie sẽ được chuyển trở lại như một phần của dữ liệu biểu mẫu. Mã từ câu trả lời trước là lấy javascript để tìm ra giá trị cookie và đưa nó vào dữ liệu biểu mẫu. Đó là một cách đáng yêu để làm điều đó từ quan điểm kỹ thuật, nhưng nó có vẻ hơi dài dòng.

Trước đây, tôi đã làm điều đó đơn giản hơn bằng cách lấy javascript để đưa giá trị mã thông báo vào dữ liệu bài đăng.

Nếu bạn sử dụng {% csrf_token%} trong mẫu của mình, bạn sẽ nhận được một trường mẫu ẩn được phát ra mang giá trị. Nhưng, nếu bạn sử dụng {{csrf_token}}, bạn sẽ chỉ nhận được giá trị trần của mã thông báo, vì vậy bạn có thể sử dụng mã này trong javascript như thế này ....

csrf_token = "{{ csrf_token }}";

Sau đó, bạn có thể bao gồm điều đó, với tên khóa được yêu cầu trong hàm băm sau đó bạn gửi dưới dạng dữ liệu cho cuộc gọi ajax.


@aehlke Bạn có thể có các tệp tĩnh. Trong mã nguồn, bạn có thể thấy một ví dụ hay, nơi bạn đăng ký các biến django trong windowđối tượng, để chúng có thể truy cập được sau đó. Ngay cả trong các tập tin tĩnh.
KitKat

3
@KitKat thực sự :) Xin lỗi vì nhận xét cổ xưa, thiếu hiểu biết của tôi ở đây. Điểm tốt.
aehlke

lại tập tin tĩnh. Không thành vấn đề, nếu bạn không phiền một chút js html của bạn. Tôi chỉ đặt {{csrf_token}} trong mẫu html chính, không xa các câu thần chú. làm việc như người ở.
JL Peyret

14

Các {% csrf_token %}mẫu html được đặt bên trong<form></form>

dịch sang một cái gì đó như:

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

vậy tại sao không chỉ grep nó trong JS của bạn như thế này:

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

và sau đó vượt qua nó, ví dụ như thực hiện một số POST, như:

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

11

Câu trả lời không phải là 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;
};

sử dụng:

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

8

Nếu biểu mẫu của bạn đăng chính xác trong Django mà không cần JS, bạn sẽ có thể tăng cường dần dần bằng ajax mà không có bất kỳ sự hack hay lộn xộn nào của mã thông báo csrf. Chỉ cần tuần tự hóa toàn bộ biểu mẫu và điều đó sẽ tự động nhận tất cả các trường biểu mẫu của bạn bao gồm cả trường csrf ẩn:

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

Tôi đã thử nghiệm điều này với Django 1.3+ và jQuery 1.5+. Rõ ràng điều này sẽ hoạt động cho bất kỳ hình thức HTML nào, không chỉ các ứng dụng Django.


5

Sử dụng Firefox với Fireorms. Mở tab 'Bảng điều khiển' trong khi thực hiện yêu cầu ajax. VớiDEBUG=True bạn nhận được trang lỗi django đẹp như phản hồi và thậm chí bạn có thể thấy html được kết xuất của phản hồi ajax trong tab bảng điều khiển.

Sau đó, bạn sẽ biết lỗi là gì.


5

Câu trả lời được chấp nhận rất có thể là cá trích đỏ. Sự khác biệt giữa Django 1.2.4 và 1.2.5 là yêu cầu về mã thông báo CSRF cho các yêu cầu AJAX.

Tôi đã gặp vấn đề này trên Django 1.3 và nguyên nhân là do cookie CSRF không được đặt ở vị trí đầu tiên. Django sẽ không đặt cookie trừ khi nó phải. Vì vậy, một trang web ajax độc quyền hoặc nặng nề chạy trên Django 1.2.4 có khả năng sẽ không bao giờ gửi mã thông báo đến máy khách và sau đó việc nâng cấp yêu cầu mã thông báo sẽ gây ra lỗi 403.

Cách khắc phục lý tưởng là ở đây: http://docs.djangoproject.com/en/dev/ref/contrib/csrf/#page-uses-ajax-without-any-html-form
nhưng bạn phải đợi 1.4 trừ khi đây chỉ là tài liệu bắt kịp với mã

Biên tập

Cũng lưu ý rằng các tài liệu Django sau lưu ý một lỗi trong jQuery 1.5, vì vậy hãy đảm bảo bạn đang sử dụng 1.5.1 trở lên với mã được đề xuất Django: http://docs.djangoproject.com/en/1.3/ref/contrib/csrf/# ajax


Câu trả lời của tôi là chính xác tại thời điểm viết nó :) Đó là ngay sau khi Django được cập nhật từ 1.2.4 đến 1.2.5. Đó cũng là khi phiên bản jQuery mới nhất là 1.5. Hóa ra nguồn gốc của vấn đề đã bị lỗi jQuery (1.5) và thông tin này hiện được thêm vào tài liệu Django, như bạn đã nêu. Trong trường hợp của tôi: cookie WAS được đặt và mã thông báo KHÔNG được thêm vào yêu cầu AJAX. Đã sửa lỗi làm việc cho jQuery 1.5 đã bị lỗi. Đến bây giờ, bạn chỉ cần bám vào các tài liệu chính thức, sử dụng mã ví dụ được cung cấp ở đó và sử dụng jQuery mới nhất. Vấn đề của bạn có nguồn khác với các vấn đề được thảo luận ở đây :)
Jakub Gocławski

2
Hiện tại có một trang trí được gọi là ensure_csrf_cookiebạn có thể bao quanh một khung nhìn để đảm bảo nó gửi cookie.
Brian Neal

Đây là vấn đề tôi gặp phải, không có csrftokencookie ở nơi đầu tiên, cảm ơn!
crhodes

5

Dường như không ai đề cập đến cách thực hiện điều này trong JS thuần bằng cách sử dụng X-CSRFTokentiêu đề và {{ csrf_token }}, vì vậy đây là một giải pháp đơn giản mà bạn không cần tìm kiếm thông qua cookie hoặc DOM:

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

4

Vì nó không được nêu ở bất kỳ đâu trong các câu trả lời hiện tại, giải pháp nhanh nhất nếu bạn không nhúng js vào mẫu của mình là:

Đặt <script type="text/javascript"> window.CSRF_TOKEN = "{{ csrf_token }}"; </script>trước tham chiếu của bạn đến tệp script.js trong mẫu của bạn, sau đó thêm csrfmiddlewaretokenvào datatừ điển của bạn trong tệp js của bạn:

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

2

Tôi vừa gặp một chút khác biệt nhưng tình huống tương tự. Không chắc chắn 100% nếu đó là giải pháp cho trường hợp của bạn, nhưng tôi đã giải quyết vấn đề cho Django 1.3 bằng cách đặt tham số POST 'csrfmiddlewaretoken' với chuỗi giá trị cookie phù hợp thường được trả về dưới dạng HTML nhà của bạn bởi Django hệ thống mẫu với thẻ '{% csrf_token%}'. Tôi đã không thử trên Django cũ, chỉ xảy ra và giải quyết trên Django1.3. Vấn đề của tôi là yêu cầu đầu tiên được gửi qua Ajax từ một biểu mẫu đã được thực hiện thành công nhưng lần thử thứ hai từ chính xác không thành công, dẫn đến trạng thái 403 ngay cả khi tiêu đề 'X-CSRFToken' cũng được đặt chính xác với giá trị mã thông báo CSRF như trong trường hợp của lần thử đầu tiên. Hi vọng điêu nay co ich.

Trân trọng,

Hiro


2

bạn có thể dán js này vào tệp html của mình, hãy nhớ đặt nó trước chức năng js khác

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


1

Một mã thông báo CSRF được gán cho mỗi phiên (tức là mỗi lần bạn đăng nhập). Vì vậy, trước khi bạn muốn nhận một số dữ liệu được nhập bởi người dùng và gửi đó dưới dạng cuộc gọi ajax đến một số chức năng được bảo vệ bởi trình trang trí csrf_protect, hãy thử tìm các chức năng được gọi trước khi bạn nhận được dữ liệu này từ người dùng. Ví dụ, một số mẫu phải được hiển thị trên đó người dùng của bạn đang nhập dữ liệu. Mẫu đó đang được kết xuất bởi một số chức năng. Trong hàm này, bạn có thể nhận mã thông báo csrf như sau: csrf = request.COOKIES ['csrftoken'] Bây giờ chuyển giá trị csrf này trong từ điển ngữ cảnh đối với mẫu nào trong câu hỏi đang được hiển thị. Bây giờ trong mẫu đó hãy viết dòng này: Bây giờ trong hàm javascript của bạn, trước khi thực hiện yêu cầu ajax, hãy viết này: var csrf = $ ('# csrf'). val () điều này sẽ chọn giá trị của mã thông báo được chuyển đến mẫu và lưu trữ nó trong biến csrf. Bây giờ trong khi thực hiện cuộc gọi ajax, trong dữ liệu bài đăng của bạn, cũng chuyển giá trị này: "csrfmiddlewaretoken": csrf

Điều này sẽ hoạt động ngay cả khi bạn không thực hiện các hình thức django.

Trong thực tế, logic ở đây là: Bạn cần mã thông báo mà bạn có thể nhận được từ yêu cầu. Vì vậy, bạn chỉ cần tìm ra chức năng được gọi ngay sau khi đăng nhập. Một khi bạn có mã thông báo này, hãy thực hiện một cuộc gọi ajax khác để nhận nó hoặc chuyển nó đến một mẫu nào đó có thể truy cập bằng ajax của bạn.


Không có cấu trúc rất tốt, nhưng giải thích tốt. Vấn đề của tôi là, tôi đã gửi csrf theo cách này : csrftoken: csrftoken, hơn là csrfmiddlwaretoken: csrftoken. Sau khi thay đổi, nó đã làm việc. Cảm ơn
gần như là người mới bắt đầu

1

cho ai đó gặp phải điều này và đang cố gắng gỡ lỗi:

1) kiểm tra django csrf (giả sử bạn đang gửi) ở đây

2) Trong trường hợp của tôi, settings.CSRF_HEADER_NAMEđã được đặt thành 'HTTP_X_CSRFTOKEN' và cuộc gọi AJAX của tôi đã gửi một tiêu đề có tên 'HTTP_X_CSRF_TOKEN' để mọi thứ không hoạt động. Tôi có thể thay đổi nó trong cuộc gọi AJAX hoặc cài đặt django.

3) Nếu bạn chọn thay đổi phía máy chủ, hãy tìm vị trí cài đặt django của bạn và ném một điểm dừng trong csrf middleware.f bạn đang sử dụng virtualenv, nó sẽ giống như:~/.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, '')

Sau đó, hãy đảm bảo csrfmã thông báo được lấy chính xác từ request.META

4) Nếu bạn cần thay đổi tiêu đề, v.v. - thay đổi biến đó trong tệp cài đặt của bạn



0

Trong trường hợp của tôi, sự cố xảy ra với cấu hình nginx mà tôi đã sao chép từ máy chủ chính sang máy chủ tạm thời với việc vô hiệu hóa https không cần thiết trên máy thứ hai trong quy trình.

Tôi đã phải bình luận hai dòng này trong cấu hình để làm cho nó hoạt động trở lại:

# uwsgi_param             UWSGI_SCHEME    https;
# uwsgi_pass_header       X_FORWARDED_PROTO;

0

Đây là một giải pháp ít dài dòng được cung cấp bởi 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>

Nguồn: https://docs.djangoproject.com/en/1.11/ref/csrf/

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.