(Django) AJAX 请求的 CSRF 验证在 Chrome 但不是 Firefox 中工作

(Django) CSRF Verification for AJAX requests working in Chrome but not Firefox

如标题所述,我的 (Django) CSRF 验证在 Chrome 中有效,但在 Firefox 中无效,我想知道为什么,以便我可以解决此问题。

我将其包含在我的 base.html 文件的 head 标记中,我的应用程序中的所有其他文件都从该文件扩展:

base.html,头部标签底部

    <script>
    $(document).ready(function() {

        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;
        }
        var csrftoken = getCookie('csrftoken');
        function csrfSafeMethod(method) {
            // these HTTP methods do not require CSRF protection
            return (/^(GET|HEAD|OPTIONS|TRACE)$/.test(method));
        }
        $.ajaxSetup({
            beforeSend: function(xhr, settings) {
                if (!csrfSafeMethod(settings.type) && !this.crossDomain) {
                    xhr.setRequestHeader("X-CSRFToken", csrftoken);
                }
            }
        });
    });
    </script>

我在一个名为 browse.js 的文件中有这段代码,它需要向我自己的服务器发出 ajax 请求。

browse.js

Template = {
  setup : function(){
    Template.events.csrf();
    // etc. etc.
  },
  events: {
    csrf : function(){
      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;
      }
      var csrftoken = getCookie('csrftoken');
      function csrfSafeMethod(method) {
        // these HTTP methods do not require CSRF protection
        return (/^(GET|HEAD|OPTIONS|TRACE)$/.test(method));
      }
      $.ajaxSetup({
        beforeSend: function(xhr, settings) {
          if (!csrfSafeMethod(settings.type) && !this.crossDomain) {
            xhr.setRequestHeader("X-CSRFToken", csrftoken);
          }
        }
      });
    },
//etc. etc.
}
//The actual ajax request
Data = {
  api : {
    ajax_get_listings : function(cb){
      var g, i, o, _ref;
      _ref = [
        $('#ci').val(), 
        $('#co').val(), 
        $('#guests').val()], 
        i = _ref[0], 
        o = _ref[1], 
        g = _ref[2];
      if (g) {
        console.log('getting listings');
        return $.ajax({
          url:'/api/get_listing_items/', 
          type: 'POST',
          datatype:'json',
          data: {
            available_start_date: i,
            available_end_date: o,
            max_guests: g
          }, 
          success: function(d) {
            if (d.listings !== null){
              Data.listings._results = [];
              console.log(d);
              var l = $.parseJSON(
                $("<textarea/>").html(d.listings).text());
              console.log(l);
              data = l;
              console.log(data);
              return cb(data);
            }else{ 
              $('#ct').text('No listings found for your search criteria. Please keep searching!');
            }
          },
        });
      }
    },
  },
//etc. etc
}

同样,这在 Chrome 中工作正常。当我在 Firefox 上时,它只会给我一个 403 Forbidden。这是回溯:

回溯

Headers

view source
Content-Type    
text/html
Date    
Mon, 19 Oct 2015 22:06:07 GMT
Server  
WSGIServer/0.1 Python/2.7.3
Vary    
Cookie
X-Frame-Options 
SAMEORIGIN
view source
Accept  
*/*
Accept-Encoding 
gzip, deflate
Accept-Language 
en-US,en;q=0.5
Cache-Control   
no-cache
Connection  
keep-alive
Content-Length  
54
Content-Type    
application/x-www-form-urlencoded; charset=UTF-8
Cookie  
_ga=GA1.1.1619904474.1445292335; _gat=1; TawkConnectionTime=0;      __tawkuuid=e||127.0.0.1||mnW1PFpM4y26O8w
+2HatshrE3nWV4w3xD7SAtEMYGtV647bMojOwsqzNlPdxYCdB||2;     Tawk_560d98fcc096ea637ec4b8c0=vs15.tawk.to:443
||0
DNT 
1
Host    
127.0.0.1:8008
Pragma  
no-cache
Referer 
http://127.0.0.1:8008/properties/
User-Agent  
Mozilla/5.0 (Windows NT 6.1; WOW64; rv:41.0) Gecko/20100101 Firefox/41.0
X-CSRFToken 
null
X-Requested-With    
XMLHttpRequest  

回应

<!DOCTYPE html>
<html lang="en">
<head>
  <meta http-equiv="content-type" content="text/html; charset=utf-8">
  <meta name="robots" content="NONE,NOARCHIVE">
  <title>403 Forbidden</title>
  <style type="text/css">
    html * { padding:0; margin:0; }
    body * { padding:10px 20px; }
    body * * { padding:0; }
    body { font:small sans-serif; background:#eee; }
    body>div { border-bottom:1px solid #ddd; }
    h1 { font-weight:normal; margin-bottom:.4em; }
    h1 span { font-size:60%; color:#666; font-weight:normal; }
    #info { background:#f6f6f6; }
    #info ul { margin: 0.5em 4em; }
    #info p, #summary p { padding-top:10px; }
    #summary { background: #ffc; }
    #explanation { background:#eee; border-bottom: 0px none; }
  </style>
</head>
<body>
<div id="summary">
  <h1>Forbidden <span>(403)</span></h1>
  <p>CSRF verification failed. Request aborted.</p>


  <p>You are seeing this message because this site requires a CSRF cookie     when submitting forms.       This
 cookie is required for security reasons, to ensure that your browser is not     being hijacked by third
 parties.</p>
      <p>If you have configured your browser to disable cookies, please re-enable them, at least for this
 site, or for &#39;same-origin&#39; requests.</p>

</div>

<div id="info">
  <h2>Help</h2>
    <p>Reason given for failure:</p>
    <pre>
      CSRF cookie not set.
    </pre>
    <p>In general, this can occur when there is a genuine Cross Site Request Forgery, or when
  <a
  href='http://docs.djangoproject.com/en/dev/ref/contrib/csrf/#ref-contrib-   csrf'>Django's
  CSRF mechanism</a> has not been used correctly.  For POST forms, you need to
  ensure:</p>

      <ul>
      <li>Your browser is accepting cookies.</li>

    <li>The view function uses <a
     href='http://docs.djangoproject.com/en/dev/ref/templates/api/#subclassing-  context-requestcontext'
><code>RequestContext</code></a>
    for the template, instead of <code>Context</code>.</li>

    <li>In the template, there is a <code>{% csrf_token
%}</code> template tag inside each POST form that
targets an internal URL.</li>

   <li>If you are not using <code>CsrfViewMiddleware</code>, then you must   use <code>csrf_protect</code> on any views that use the <code>csrf_token</code>
template tag, as well as those that accept the POST data.</li>
</ul><p>You're seeing the help section of this page because you have <code>DEBUG =
  True</code> in your Django settings file. Change that to   <code>False</code>,
 and only the initial error message will be displayed.  </p>

   <p>You can customize this page using the CSRF_FAILURE_VIEW setting.</p>
</div>

</body>
</html>

有什么问题吗?


已解决:我将 @ensure_csrf_cookie 放在获取 cookie 的视图上(不是 ajax 请求调用的函数——这让我感到困惑)。现在 Firefox 上不再有 403。耶

在您的请求中 headers,我看到:

X-CSRFToken null

所以我猜测是在 Firefox 中设置了 cookie。也许它已经从之前的 session.

设置在 Chrome 中

The Django docs explain one reason why this may be:

Warning

If your view is not rendering a template containing the csrf_token template tag, Django might not set the CSRF token cookie. This is common in cases where forms are dynamically added to the page. To address this case, Django provides a view decorator which forces setting of the cookie: ensure_csrf_cookie().

尝试在您的 views.py 中导入 ensure_csrf_cookie 装饰器并用它包装您的基础视图。例如:

from django.views.decorators.csrf import ensure_csrf_cookie

@ensure_csrf_cookie
def base_view(request):
    # do stuff
    return render('base.html', {...})

我不确定这是否是根本问题,但我希望这对您有所帮助!