簡體   English   中英

Django CSRF 檢查因 Ajax POST 請求而失敗

[英]Django CSRF check failing with an Ajax POST request

我可以通過我的 AJAX 帖子使用一些幫助來遵守 Django 的 CSRF 保護機制。 我已按照此處的說明進行操作:

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

我已經完全復制了他們在該頁面上的 AJAX 示例代碼:

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

我在xhr.setRequestHeader調用之前放置了一個打印getCookie('csrftoken')內容的警報,它確實填充了一些數據。 我不確定如何驗證令牌是否正確,但我很高興它正在查找和發送一些東西。

但是 Django 仍然拒絕我的 AJAX 帖子。

這是我的 JavaScript:

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

這是我從 Django 看到的錯誤:

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

我確定我錯過了一些東西,也許它很簡單,但我不知道它是什么。 我搜索了 SO 並看到了一些關於通過csrf_exempt裝飾器關閉我的視圖的 CSRF 檢查的信息,但我發現這沒有吸引力。 我已經嘗試過了,它可以工作,但如果可能的話,我寧願讓我的 POST 以 Django 設計的方式工作。

以防萬一它有幫助,這是我的觀點的要點:

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

感謝您的回復!

如果使用$.ajax函數,只需在數據體中添加csrf標記即可:

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

真正的解決方案

好的,我設法追查了問題。 它存在於 Javascript(正如我在下面建議的)代碼中。

你需要的是這個:

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

而不是官方文檔中發布的代碼: https : //docs.djangoproject.com/en/2.2/ref/csrf/

工作代碼,來自這個 Django 條目: http : //www.djangoproject.com/weblog/2011/feb/08/security/

所以一般的解決方案是:“使用 ajaxSetup 處理程序而不是 ajaxSend 處理程序”。 我不知道它為什么有效。 但它對我有用:)

上一篇(沒有回答)

我實際上遇到了同樣的問題。

它發生在更新到 Django 1.2.5 之后 - Django 1.2.4 中的 AJAX POST 請求沒有錯誤(AJAX 沒有以任何方式受到保護,但它工作得很好)。

就像 OP 一樣,我嘗試了 Django 文檔中發布的 JavaScript 片段。 我正在使用 jQuery 1.5。 我也在使用“django.middleware.csrf.CsrfViewMiddleware”中間件。

我試圖遵循中間件代碼,但我知道它失敗了:

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

進而

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

這個“if”是真的,因為“request_csrf_token”是空的。

基本上這意味着未設置標題。 那么這個 JS 行有什么問題嗎:

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

?

我希望提供的詳細信息可以幫助我們解決問題:)

將此行添加到您的 jQuery 代碼中:

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

並做了。

{% csrf_token %}放在<form></form>內的 html 模板中

翻譯成類似的東西:

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

那么為什么不像這樣在你的 JS 中 grep 呢:

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

然后通過它,例如做一些 POST,比如:

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

問題是因為 django 期望來自 cookie 的值作為表單數據的一部分傳回。 上一個答案中的代碼是讓 javascript 找出 cookie 值並將其放入表單數據中。 從技術角度來看,這是一種很好的方法,但它看起來確實有點冗長。

過去,我通過讓 javascript 將令牌值放入 post 數據來更簡單地完成它。

如果您在模板中使用 {% csrf_token %},您將獲得一個帶有該值的隱藏表單字段。 但是,如果你使用 {{ csrf_token }} 你只會得到令牌的裸值,所以你可以像這樣在 javascript 中使用它......

csrf_token = "{{ csrf_token }}";

然后,您可以將其包含在散列中所需的鍵名中,然后將其作為數據提交給 ajax 調用。

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

用法:

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

似乎沒有人提到如何使用X-CSRFToken標頭和{{ csrf_token }}在純 JS 中執行此操作,因此這里有一個簡單的解決方案,您無需搜索 cookie 或 DOM:

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

如果您的表單在沒有 JS 的情況下在 Django 中正確發布,您應該能夠使用 ajax 逐步增強它,而無需任何黑客攻擊或混亂的 csrf 令牌傳遞。 只需序列化整個表單,它就會自動獲取所有表單字段,包括隱藏的 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;
});

我已經用 Django 1.3+ 和 jQuery 1.5+ 對此進行了測試。 顯然,這適用於任何 HTML 表單,而不僅僅是 Django 應用程序。

接受的答案很可能是一個紅鯡魚。 Django 1.2.4 和 1.2.5 之間的區別在於 AJAX 請求需要 CSRF 令牌。

我在 Django 1.3 上遇到了這個問題,它是首先沒有設置 CSRF cookie 引起的 除非必須,Django 不會設置 cookie。 因此,在 Django 1.2.4 上運行的完全或大量 ajax 站點可能永遠不會向客戶端發送令牌,然后需要令牌的升級將導致 403 錯誤。

理想的修復方法在這里: http : //docs.djangoproject.com/en/dev/ref/contrib/csrf/#page-uses-ajax-without-any-html-form
但你必須等待 1.4 除非這只是趕上代碼的文檔

編輯

另請注意,后來的 Django 文檔指出了 jQuery 1.5 中的一個錯誤,因此請確保您使用的是 1.5.1 或更高版本的 Django 建議代碼: https : //docs.djangoproject.com/en/dev/ref/csrf/#ajax

將 Firefox 與 Firebug 一起使用。 在觸發 ajax 請求時打開“控制台”選項卡。 使用DEBUG=True您將獲得漂亮的 django 錯誤頁面作為響應,您甚至可以在控制台選項卡中看到 ajax 響應的呈現 html。

然后你就會知道錯誤是什么。

由於當前答案中沒有任何說明,如果您沒有將js嵌入到模板中,最快的解決方案是:

<script type="text/javascript"> window.CSRF_TOKEN = "{{ csrf_token }}"; </script> <script type="text/javascript"> window.CSRF_TOKEN = "{{ csrf_token }}"; </script>在您引用模板中的 script.js 文件之前,然后將csrfmiddlewaretoken添加到您的 js 文件中的data字典中:

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

你可以把這個js粘貼到你的html文件中,記得把它放在其他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>

我剛剛遇到了一些不同但相似的情況。 不是 100% 確定它是否可以解決您的情況,但我通過使用正確的 cookie 值字符串設置 POST 參數“csrfmiddlewaretoken”解決了 Django 1.3 的問題,該字符串通常由 Django 以您的主頁 HTML 的形式返回帶有“{% csrf_token %}”標簽的模板系統。 我沒有嘗試使用較舊的 Django,只是在 Django1.3 上發生並解決了。 我的問題是,通過 Ajax 從表單提交的第一個請求已成功完成,但從完全相同的第二次嘗試失敗,導致 403 狀態,即使標頭“X-CSRFToken”也與 CSRF 令牌值正確放置就像第一次嘗試一樣。 希望這可以幫助。

問候,

使用 Django 輕松調用 ajax

(26.10.2020)
在我看來,這比正確答案更清晰、更簡單。

風景

@login_required
def some_view(request):
    """Returns a json response to an ajax call. (request.user is available in view)"""
    # Fetch the attributes from the request body
    data_attribute = request.GET.get('some_attribute')  # Make sure to use POST/GET correctly
    # DO SOMETHING...
    return JsonResponse(data={}, status=200)

網址.py

urlpatterns = [
    path('some-view-does-something/', views.some_view, name='doing-something'),
]

阿賈克斯調用

ajax 調用非常簡單,但對於大多數情況來說已經足夠了。 您可以獲取一些值並將它們放入數據對象中,然后在上面描述的視圖中,您可以通過它們的名稱再次獲取它們的值。

您可以在django 的文檔中找到 csrftoken 函數。 基本上只需復制它並確保它在您的 ajax 調用之前呈現,以便定義csrftoken 變量

$.ajax({
    url: "{% url 'doing-something' %}",
    headers: {'X-CSRFToken': csrftoken},
    data: {'some_attribute': some_value},
    type: "GET",
    dataType: 'json',
    success: function (data) {
        if (data) {
            console.log(data);
            // call function to do something with data
            process_data_function(data);
        }
    }
});

使用ajax將HTML添加到當前頁面

這可能有點離題,但我很少看到它被使用,它是最小化窗口重定位以及在 javascript 中手動創建 html 字符串的好方法。

這與上面的非常相似,但這次我們從響應中渲染 html 而不重新加載當前窗口。

如果您打算從接收到的數據中呈現某種 html 作為對 ajax 調用的響應,那么從視圖發送回 HttpResponse 而不是 JsonResponse 可能更容易。 這使您可以輕松創建 html,然后可以將其插入到元素中。

風景

# The login required part is of course optional
@login_required
def create_some_html(request):
    """In this particular example we are filtering some model by a constraint sent in by 
    ajax and creating html to send back for those models who match the search"""
    # Fetch the attributes from the request body (sent in ajax data)
    search_input = request.GET.get('search_input')

    # Get some data that we want to render to the template
    if search_input:
        data = MyModel.objects.filter(name__contains=search_input) # Example
    else:
        data = []

    # Creating an html string using template and some data
    html_response = render_to_string('path/to/creation_template.html', context = {'models': data})

    return HttpResponse(html_response, status=200)

用於視圖的 html 創建模板

creation_template.html

{% for model in models %}
   <li class="xyz">{{ model.name }}</li>
{% endfor %}

網址.py

urlpatterns = [
    path('get-html/', views.create_some_html, name='get-html'),
]

主模板和ajax調用

這是我們要將數據添加到的模板。 特別是在這個例子中,我們有一個搜索輸入和一個將搜索輸入的值發送到視圖的按鈕。 然后視圖發送一個 HttpResponse 返回顯示與我們可以在元素內呈現的搜索匹配的數據。

{% extends 'base.html' %}
{% load static %}
{% block content %}
    <input id="search-input" placeholder="Type something..." value="">
    <button id="add-html-button" class="btn btn-primary">Add Html</button>
    <ul id="add-html-here">
        <!-- This is where we want to render new html -->
    </ul>
{% end block %}

{% block extra_js %}
    <script>
        // When button is pressed fetch inner html of ul
        $("#add-html-button").on('click', function (e){
            e.preventDefault();
            let search_input = $('#search-input').val();
            let target_element = $('#add-html-here');
            $.ajax({
                url: "{% url 'get-html' %}",
                headers: {'X-CSRFToken': csrftoken},
                data: {'search_input': search_input},
                type: "GET",
                dataType: 'html',
                success: function (data) {
                    if (data) {
                        /* You could also use json here to get multiple html to
                        render in different places */
                        console.log(data);
                        // Add the http response to element
                        target_element.html(data);
                    }
                }
            });
        })
    </script>
{% endblock %}

在我的情況下,問題出在我從主服務器復制到臨時配置的 nginx 配置上,並禁用了進程中第二個不需要的 https。

我不得不在配置中注釋掉這兩行以使其再次工作:

# uwsgi_param             UWSGI_SCHEME    https;
# uwsgi_pass_header       X_FORWARDED_PROTO;

每個會話(即每次登錄時)都會分配一個 CSRF 令牌。 因此,在您希望獲取用戶輸入的一些數據並將其作為 ajax 調用發送到某個受 csrf_protect 裝飾器保護的函數之前,請嘗試在從用戶獲取此數據之前找到正在調用的函數。 例如,必須呈現一些模板,您的用戶正在其上輸入數據。 該模板正在由某個函數呈現。 在此函數中,您可以按如下方式獲取 csrf 令牌: csrf = request.COOKIES['csrftoken'] 現在將此 csrf 值傳遞到正在呈現相關模板的上下文字典中。 現在在該模板中寫下這一行:現在在您的 javascript 函數中,在發出 ajax 請求之前,寫下: var csrf = $('#csrf').val() 這將選擇傳遞給模板的令牌值並將其存儲在變量中csrf。 現在,在進行 ajax 調用時,在您的帖子數據中,也傳遞此值:"csrfmiddlewaretoken": csrf

即使您沒有實現 django 表單,這也將起作用。

實際上,這里的邏輯是:您需要可以從請求中獲取的令牌。 所以你只需要找出登錄后立即調用的函數。一旦你有了這個令牌,要么進行另一個 ajax 調用來獲取它,要么將它傳遞給你的 ajax 可以訪問的某個模板。

對於遇到此問題並試圖調試的人:

1)django csrf 檢查(假設你發送一個)在這里

2) 在我的例子中, settings.CSRF_HEADER_NAME被設置為 'HTTP_X_CSRFTOKEN' 並且我的 AJAX 調用發送了一個名為 'HTTP_X_CSRF_TOKEN' 的標頭,所以東西不起作用。 我可以在 AJAX 調用或 django 設置中更改它。

3)如果您選擇在服務器端更改它,請找到 django 的安裝位置並在csrf middleware設置斷點。如果您使用的是virtualenv ,它將類似於: ~/.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, '')

然后,確保csrf令牌正確來自 request.META

4)如果您需要更改標題等 - 在您的設置文件中更改該變量

如果有人正在努力使用 axios 來完成這項工作,這對我有幫助:

import axios from 'axios';

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

資料來源: https : //cbuelter.wordpress.com/2017/04/10/django-csrf-with-axios/

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

來源: https : //docs.djangoproject.com/en/1.11/ref/csrf/

選擇的答案相關,只想添加到選擇的答案中。

在那個答案中,關於.ajaxSetup(...)的解決方案。 在你的 Django settings.py 中,如果你有

CSRF_USE_SESSIONS = True

它會導致所選的答案根本不起作用。 在實施所選答案的解決方案時,刪除該行或將其設置為 False 對我有用。

有趣的是,如果您在 Django settings.py 中設置以下內容

CSRF_COOKIE_HTTPONLY = True

此變量不會導致所選答案的解決方案停止運行。

CSRF_USE_SESSIONSCSRF_COOKIE_HTTPONLY都來自這個官方 Django 文檔https://docs.djangoproject.com/en/2.2/ref/csrf/

(我沒有足夠的代表發表評論,所以我發布了我的輸入答案)

我有一個解決方案。 在我的 JS 中,我有兩個功能。 首先獲取 Cookies(即 csrftoken):

function getCookie(name) {
let cookieValue = null;
if (document.cookie && document.cookie !== '') {
    const cookies = document.cookie.split(';');
    for (let i = 0; i < cookies.length; i++) {
        const cookie = cookies[i].trim();
        // 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;

}

第二個是我的 ajax 函數。 在這種情況下,它用於登錄,實際上不返回任何內容,只需傳遞值來設置會話:

function LoginAjax() {


    //get scrftoken:
    const csrftoken = getCookie('csrftoken');

    var req = new XMLHttpRequest();
    var userName = document.getElementById("Login-Username");
    var password = document.getElementById("Login-Password");

    req.onreadystatechange = function () {
        if (this.readyState == 4 && this.status == 200) {            
            //read response loggedIn JSON show me if user logged in or not
            var respond = JSON.parse(this.responseText);            
            alert(respond.loggedIn);

        }
    }

    req.open("POST", "login", true);

    //following header set scrftoken to resolve problem
    req.setRequestHeader('X-CSRFToken', csrftoken);

    req.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
    req.send("UserName=" + userName.value + "&Password=" + password.value);
}

使用 Django 3.1.1 和我嘗試過的所有解決方案都失敗了。 但是,將“csrfmiddlewaretoken”鍵添加到我的 POST 正文中是有效的。 這是我打的電話:

$.post(url, {
  csrfmiddlewaretoken: window.CSRF_TOKEN,
  method: "POST",
  data: JSON.stringify(data),
  dataType: 'JSON',
});

在 HTML 模板中:

<script type="text/javascript">
  window.CSRF_TOKEN = "{{ csrf_token }}";
</script>

2022 年更新

在 CSRF 攻擊中,無辜的最終用戶被攻擊者欺騙,提交了他們無意的 Web 請求

選項1

from django.views.decorators.csrf import csrf_exempt
from django.http.response import JsonResponse


@csrf_exempt
def commentDeletePost(request):
    if request.is_ajax() and request.method == 'POST':
        try:
            comment = Comment.objects.get(pk=request.POST['pk'])
            if comment.author != request.user:
                return JsonResponse({'e': 'Forbidden'}, status=403) 
            comment.delete()
            return JsonResponse({}, status=200)
        execpt Comment.DoesNotExist:
            return JsonResponse({'e': 'Not Found'}, status=404)

選項 2

<div id="csrf">
    {% csrf_token %}
</div>
<script type="text/javascript">
    window.crud = {
        commentDelete: function(
            pk, 
            success,
            error, 
        ){
            $.ajax({
                headers: {'X-CSRFToken': document.getElementById('csrf').querySelector('input').value},
                type: "POST", 
                url: "{% url 'comment-delete-post' %}",
                data: {
                    pk: pk,
                }, 
                success: success, 
                error: error,
            })
        }, 
    }
</script>

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM