<form action="", method="post">
{% csrf_token %}
</form>
如果是前后端不分离的项目,django 为了防止 XSS 攻击,要求所有 form 表单提交时加上 {% csrf_token %}
标签,前端会再 html 中加入一个 input 标签
django.middleware.csrf.CsrfViewMiddleware
这个中间件就是来验证 csrf_token 如果没有加,就会出错,这个 token 每次刷新页面时都会刷新,客户在请求时。会再服务器端生成一个 token,然后放在 cookie里面,每次 post 请求都会带上这个 token,去和表单里面的去对比。
官⽅⽂档中说到,检验token时,只⽐较secret是否和cookie中的secret值⼀样,⽽不是⽐较整个token。
token字符串的前32位是salt,后⾯是加密后的token,通过salt能解密出唯⼀的secret。
使用表单时,必须在每个 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 为例:
想必都看到过这个报错:
这就是没有加 csrf_token ,要么是表单中没加,要么是没获取到 token
重点都在 django.middleware.csrf.CsrfViewMiddleware
这个中间件里面,导入之后点进去看。
# 从请求的cookie中获取csrftoken的值
# 一般是一个键值对,key是 csrftoken vlaue 是一串随机字符串
csrf_token = request.META.get('CSRF_COOKIE')
if csrf_token is None:
# No CSRF cookie. For POST requests, we insist on a CSRF cookie,
# and in this way we can avoid all CSRF attacks, including login
# CSRF.
# 如果获取不到,返回:CSRF cookie not set. 就是没有设置 cookie 的意思
return self._reject(request, REASON_NO_CSRF_COOKIE)
# Check non-cookie token for match.
request_csrf_token = ""
if request.method == "POST":
try:
# 从 post 里面获取 token
request_csrf_token = request.POST.get('csrfmiddlewaretoken', '')
except IOError:
# Handle a broken connection before we've completed reading
# the POST data. process_view shouldn't raise any
# exceptions, so we'll ignore and serve the user a 403
# (assuming they're still listening, which they probably
# aren't because of the error).
pass
# 这里比较重要了
if request_csrf_token == "":
# Fall back to X-CSRFToken, to make things easier for AJAX,
# and possible for PUT/DELETE.
# 如果 request_csrf_token 里面没有,就去请求头中去拿 CSRF_HEADER_NAME
# 默认是 X_CSRFTOKEN, 名字也可以在 setting 中自己设置
request_csrf_token = request.META.get(settings.CSRF_HEADER_NAME, '')
request_csrf_token = _sanitize_token(request_csrf_token)
# 校验表单中的 token 和 cookie 中的token是不是一致
# 检验token时,只⽐较secret是否和cookie中的secret值⼀样
# 这个 secret 应该是配置文件中的 SECRET_KEY
if not _compare_salted_tokens(request_csrf_token, csrf_token):
# 比较失败,返回一个页面,内容就是我们熟悉的:CSRF token missing or incorrect.
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_NAME = 'csrftoken' # 默认的 key 名称
CSRF_COOKIE_AGE = 60 * 60 * 24 * 7 * 52 # 存活时间
CSRF_COOKIE_DOMAIN = None # 在那个域名下生效
CSRF_HEADER_NAME = 'HTTP_X_CSRFTOKEN' # 请求头的名称
每个 django 版本可能稍有不同
def _compare_salted_tokens(request_csrf_token, csrf_token):
# Assume both arguments are sanitized -- that is, strings of
# length CSRF_TOKEN_LENGTH, all CSRF_ALLOWED_CHARS.
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) # Note negative values are ok
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