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的东西,也就是“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_SESSIONS
和CSRF_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_SESSIONS
和CSRF_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视频。
有时候,我们在全站上关闭了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): ...
有正就有反。在全站开启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()
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')
这个装饰器类似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读取cookieCSRF_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默认即可。
在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奖章 博主童心未泯 很皮
是的。^_^