CSRF与AJAX

阅读: 44097     评论:5

CSRF(Cross-site request forgery)跨站请求伪造,是一种常见的网络攻击手段,具体内容和含义请大家自行百度。

Django为我们提供了防范CSRF攻击的机制。

一、基本使用

默认情况下,使用django-admin startproject xxx命令创建工程时,CSRF防御机制就已经开启了。如果没有开启,请在MIDDLEWARE设置中添加'django.middleware.csrf.CsrfViewMiddleware'。

对于GET请求,一般来说没有这个问题,CSRF通常是针对POST方法的!

在含有POST表单的模板中,只需要在其<form>表单内部添加csrf_token标签,如下所示:

<form action="" method="post">
    {% csrf_token %}
    ....
</form>

这样,实际上就是生成了一个隐藏的名称为'csrfmiddlewaretoken'的input输入框,这个input的值就是Django提供给表单的csrf_token。

当表单数据通过POST方法,发送到后台服务器的时候,除了正常的表单数据外,还会携带这个CSRF令牌随机字符串,用于进行csrf验证。

其实没有多么麻烦和复杂,对么?如果表单中没有携带这个csrf令牌,你将会获得一枚403奖章。

额外提示:对于初学者,要明白一件事情,就是我们上面讲的都是Django项目自己内部的事务,不涉及与外界的关系。例如,你不能把上面那个表单发往百度,百度会懵逼的,你这发的啥?其次,那样也不安全,可能引起CSRF信息泄露而导致自己的站点出现漏洞。

二、 AJAX

我们知道,在前端的世界,有一种叫做AJAX的东西,也就是“Asynchronous Javascript And XML”(异步 JavaScript 和 XML),经常被用来在不刷新页面的情况下,提交和请求数据。如果我们的Django服务器接收的是一个通过AJAX发送过来的POST请求的话,那么将很麻烦。

为什么?因为AJAX中,没有办法像form表单中那样通过一个隐藏的input标签携带{% csrf_token %}令牌。

那怎么办呢?其实也很好办。

首先我们要知道CSRF中间件会在cookie中写入CSRF令牌随机字符串。

我们只需要通过JS代码获取这个字符串,然后随同AJAX发送到后台服务器即可。

这个随同的过程也很简单,很多JS框架都提供修改HTTP头部的钩子,我们可以在header中添加X-CSRFToken键值对,值就是CSRF令牌。键的名字可以通过 CSRF_HEADER_NAME配置项自定义,但一般保持默认值就好。

然后第一步是获取cookie中的CSRF令牌,不过这取决于两个配置项:

  • CSRF_USE_SESSIONSCSRF_COOKIE_HTTPONLY 都是False的时候

CSRF_USE_SESSIONS 为True表示将csrf的令牌储存在会话中。

CSRF_COOKIE_HTTPONLY 为True表示客户端的JS代码不能访问cookie。

一般来说,大多Web服务器都会保持这两个设置为False,因为True没什么实际意义。

此时直接去cookie中读取CSRF令牌即可(将下面的代码抄到你的HTML中):

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;
}
const csrftoken = getCookie('csrftoken');

如果你安装了js-cookie前端库,那么上面的代码可以简化为:

const csrftoken = Cookies.get('csrftoken');

'csrftoken'是默认情况下CSRF令牌储存在cookie中的键,可以通过CSRF_COOKIE_NAME设置来自定义名称。

注意,为了保证在没有表单系统的时候,Django向cookie中写入了CSRF令牌,你需要在视图上使用装饰器django.views.decorators.csrf.ensure_csrf_cookie

  • CSRF_USE_SESSIONSCSRF_COOKIE_HTTPONLY 有一个是True的时候

这个时候显然,cookie中即使有CSRF令牌也读取不到。

我们需要在Django进行render(request, 'xxx.html',{})的时候,在HTML文件中显式地添加一个csrf_token到DOM中,然后通过JS代码获得它,如下所示:

{% csrf_token %}
<script>
const csrftoken = document.querySelector('[name=csrfmiddlewaretoken]').value;
</script>

现在,我们通过JS手段成功获取到了CSRF令牌,然后就是通过AJAX在发送POST数据的同时一起发送它了:

const request = new Request(
    /* URL */,
    {headers: {'X-CSRFToken': csrftoken}}
);
fetch(request, {
    method: 'POST',
    mode: 'same-origin'  // Do not send CSRF token to another domain.
}).then(function(response) {
    // ...
});

以上是ES6新语法(不再使用JQuery了),如果不熟悉的可以自行学习或者观看我的Vue视频。

三、装饰器

1. 单独指定csrf验证需要

有时候,我们在全站上关闭了CSRF功能,但是希望某些视图还有CSRF防御,那怎么办呢?

Django为我们提供了一个csrf_protect(view)装饰器,使用起来非常方便,如下所示:

from django.shortcuts import render
from django.views.decorators.csrf import csrf_protect

@csrf_protect
def my_view(request):
    c = {}
    # ...
    return render(request, "a_template.html", c)

现在,虽然全站关掉了csrf,但是my_view视图依然需要进行csrf验证。

另外,当你缓存某个视图的时候,由于缓存的机制,你必须显式的为需要csrf保护的视图添加此装饰器:

from django.views.decorators.csrf import csrf_protect

@cache_page(60 * 15)
@csrf_protect
def my_view(request):
    ...

2. 单独指定忽略csrf验证

有正就有反。在全站开启CSRF机制的时候,有些视图我们并不想开启这个功能。比如,有另外一台机器通过requests库,模拟HTTP通信,以POST请求向我们的Django主机服务器发送过来了一段保密数据。它无法携带CSRF令牌,必然会被403。

这怎么办呢?

在接收这个POST请求的视图上为CSRF开道口子,不进行验证。这就需要使用Django为我们提供的csrf_exempt(view)装饰器了,下面是使用范例:

from django.views.decorators.csrf import csrf_exempt
from django.http import HttpResponse

@csrf_exempt
def my_view(request):
    return HttpResponse('Hello world')

这下POST数据是没问题了,但是又带来了新的安全问题,需要你自己处理。

最常见的用法如下:

from django.views.decorators.csrf import csrf_exempt, csrf_protect

@csrf_exempt  # 外面忽略csrf
def my_view(request):

    @csrf_protect  # 里面需要csrf
    def protected_path(request):
        do_something()

    if some_condition():
       return protected_path(request)
    else:
       do_something_else()

3. 确保csrf令牌被设置

Django还提供了一个装饰器,确保被装饰的视图在返回HTML页面给前端用户时同时将csrf令牌写入Cookie。

这个装饰器是:ensure_csrf_cookie(view),其使用方法和上面的一样:

from django.views.decorators.csrf import ensure_csrf_cookie
from django.http import HttpResponse

@ensure_csrf_cookie
def my_view(request):
    return HttpResponse('Hello world')

4. requires_csrf_token(view)

这个装饰器类似csrf_protect,一样要进行csrf验证,但是它不会拒绝发送过来的请求。

from django.views.decorators.csrf import requires_csrf_token
from django.shortcuts import render

@requires_csrf_token
def my_view(request):
    c = {}
    # ...
    return render(request, "a_template.html", c)

四、配置项

下面是Django中可配置的关于CSRF的settings:

  • CSRF_COOKIE_AGE: cookie的有效期
  • CSRF_COOKIE_DOMAIN: 允许访问的域名
  • CSRF_COOKIE_HTTPONLY:是否允许JS读取cookie
  • CSRF_COOKIE_NAME: cookie的键
  • CSRF_COOKIE_PATH:cookie的位置
  • CSRF_COOKIE_SAMESITE
  • CSRF_COOKIE_SECURE:将此设置为 True,避免不小心使用 HTTP 传输 CSRF cookie。
  • CSRF_FAILURE_VIEW:csrf拒绝后跳转的视图
  • CSRF_HEADER_NAME:csrf在header中的键
  • CSRF_TRUSTED_ORIGINS :信任源
  • CSRF_USE_SESSIONS:是否使用基于session的csrf令牌

大多情况下,以上都不需要配置,保持Django默认即可。


 Authentication 国际化和本地化 

评论总数: 5


点击登录后方可评论

在Ajax中这样,beforeSend: function (xhr, settings) { xhr.setRequestHeader("X-CSRFToken", "{{ csrf_token }}"); },也可以的。



现在似乎不用这么麻烦了: $('#id_image').on('change', function () { var currentpath = window.location.pathname; var formData = new FormData($('form')[0]); formData.append('csrfmiddlewaretoken', '{{ csrf_token }}'); $.ajax({ url: currentpath, //server script to process data type: 'POST', data: formData, cache: false, contentType: false, processData: false }); }); 上面是溢栈网上的,使用 FormData对象就好了,我自己测试的时候,直接用 FormData 就解决了



博主,put和delete请求在django正怎么使用呢?



如果表单中没有携带这个csrf令牌,你将会获得一枚403奖章 博主童心未泯 很皮



是的。^_^