表单提交时
<form action="", method="post">
{% csrf_token %}
</form>
如果是前后端不分离的项目,django 为了防止 XSS 攻击,要求所有 form 表单提交时加上 {% csrf_token %} 标签,前端会再 html 中加入一个 input 标签<input type=“hidden”, name=‘csrfmiddlewaretoken’ value=服务器随机生成的token> django.middleware.csrf.CsrfViewMiddleware 这个中间件就是来验证 csrf_token 如果没有加,就会出错,这个 token 每次刷新页面时都会刷新,客户在请求时。会再服务器端生成一个 token,然后放在 cookie里面,每次 post 请求都会带上这个 token,去和表单里面的去对比。
-
官??档中说到,检验token时,只?较secret是否和cookie中的secret值?样,?不是?较整个token。 -
token字符串的前32位是salt,后?是加密后的token,通过salt能解密出唯?的secret。
AJAX 提交时
使用表单时,必须在每个 post 上添加 csrf_token 标签,不够方便,处于这个原因,有一种替代方案,设置一个自定义的 X-CSRFToken 头(由 CSRF_HEADER_NAME 设置指定)为 CSRF 标记的值。这通常比较容易,因为许多 JavaScript 框架提供了钩子,允许在每个请求中设置头。 CSRF 令牌 cookie 默认命名为 csrftoken,但你可以通过 CSRF_COOKIE_NAME 配置来控制 cookie 的名称。 可以通过这样的方式获得 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;
}
const csrftoken = getCookie('csrftoken');
后续可以使用这个函数来获取 csrftoken:
const csrftoken = Cookies.get('csrftoken');
在请求头中设置令牌:
const request = new Request(
/* URL */,
{
method: 'POST',
headers: {'X-CSRFToken': csrftoken},
mode: 'same-origin' // Do not send CSRF token to another domain.
}
);
fetch(request).then(function(response) {
// ...
});
后续再请求时需要带上 X-CSRFToken 这个头,以postman 为例:
CsrfViewMiddleware 源码
想必都看到过这个报错: 这就是没有加 csrf_token ,要么是表单中没加,要么是没获取到 token 重点都在 django.middleware.csrf.CsrfViewMiddleware 这个中间件里面,导入之后点进去看。
csrf_token = request.META.get('CSRF_COOKIE')
if csrf_token is None:
return self._reject(request, REASON_NO_CSRF_COOKIE)
request_csrf_token = ""
if request.method == "POST":
try:
request_csrf_token = request.POST.get('csrfmiddlewaretoken', '')
except IOError:
pass
if request_csrf_token == "":
request_csrf_token = request.META.get(settings.CSRF_HEADER_NAME, '')
request_csrf_token = _sanitize_token(request_csrf_token)
if not _compare_salted_tokens(request_csrf_token, csrf_token):
return self._reject(request, REASON_BAD_TOKEN)
装饰器方法
csrf_protect : 强制执行 csrf 验证
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_exempt:免除 csrf 的保护
from django.http import HttpResponse
from django.views.decorators.csrf import csrf_exempt
@csrf_exempt
def my_view(request):
return HttpResponse('Hello world')
对于 CSRF cookie 常用的一些全局变量
CSRF_COOKIE_NAME = 'csrftoken'
CSRF_COOKIE_AGE = 60 * 60 * 24 * 7 * 52
CSRF_COOKIE_DOMAIN = None
CSRF_HEADER_NAME = 'HTTP_X_CSRFTOKEN'
CSRFTOKEN 比较方式源码
每个 django 版本可能稍有不同
def _compare_salted_tokens(request_csrf_token, csrf_token):
return constant_time_compare(
_unsalt_cipher_token(request_csrf_token),
_unsalt_cipher_token(csrf_token),
)
def _unsalt_cipher_token(token):
"""
Given a token (assumed to be a string of CSRF_ALLOWED_CHARS, of length
CSRF_TOKEN_LENGTH, and that its first half is a salt), use it to decrypt
the second half to produce the original secret.
"""
salt = token[:CSRF_SECRET_LENGTH]
token = token[CSRF_SECRET_LENGTH:]
chars = CSRF_ALLOWED_CHARS
pairs = zip((chars.index(x) for x in token), (chars.index(x) for x in salt))
secret = ''.join(chars[x - y] for x, y in pairs)
return secret
参考文献
django 官网:https://docs.djangoproject.com/zh-hans/4.0/ref/csrf/ Django之同源和跨域、CSRF详解:https://blog.csdn.net/qq_39253370/article/details/105684890?spm=1001.2014.3001.5501
|