我搜索过其他类似的问题,但没有一个解决方案奏效,也没有让我深入了解可能发生的事情。
我的设置是Vue前端(有自己的路由(加上Django后端和API。我尝试的任何GET路由都能按预期工作,但POST路由需要CSRF保护。我有一个自定义的呈现函数,我在路由上调用它,它将被带到索引(然后由Vue处理(,在那里我提供CSRF令牌,如下所示:
def custom_render(request):
# ...
# from django.middleware.csrf
get_token(request)
# from django.shortcuts
return render(request, template)
这设置了一个带有CSRF令牌的cookiecsrftoken
,它似乎工作正常,正如我在devtools中看到的那样,如果我删除了它,当我刷新时它会再次出现。以下是我的相关Djangosettings.py
:
# This one is True in production, but for now I'm testing locally
CSRF_COOKIE_SECURE = False
CSRF_HEADER_NAME = "X-CSRFToken"
# I tried playing with both these options' values to no avail
CSRF_USE_SESSIONS = False
CSRF_COOKIE_HTTPONLY = False
MIDDLEWARE = [
"django.middleware.security.SecurityMiddleware",
"django.contrib.sessions.middleware.SessionMiddleware",
"django.middleware.common.CommonMiddleware",
"django.middleware.csrf.CsrfViewMiddleware",
"django.contrib.auth.middleware.AuthenticationMiddleware",
"django.contrib.messages.middleware.MessageMiddleware",
"django.middleware.clickjacking.XFrameOptionsMiddleware",
"django.middleware.common.BrokenLinkEmailsMiddleware",
]
在Vue方面(Typescript(,我使用以下内容发送请求:
function getCsrfToken() {
return document.cookie.match("(^|;)\s*" + "csrftoken" + "\s*=\s*([^;]+)")?.pop()
}
fetch(
"api/my-route/",
{
method: "POST",
headers: {
"X-CSRFToken": getCsrfToken(),
"Content-Type": "application/json",
"Accept": "application/json",
},
mode: "same-origin",
body: JSON.stringify(dataToSend),
},
)
这也可以按预期工作,正如我在devtools中看到的那样,请求确实包含X-CSRFToken
标头,其内容与csrftoken
cookie相同。然而,响应仍然是403,声称令牌丢失或不正确。我不确定Django是认为它丢失了,还是认为它不正确,所以我不确定如何继续。连接调试器是不切实际的,因为我不知道应该在什么内部方法停止执行,所以我陷入了困境。
编辑:我最近尝试过的其他东西:
- ,在我的主视图上用
@ensure_csrf_cookie
装饰器替换了在我的自定义渲染函数中使用get_token()
。奇怪的是,这并没有导致按预期设置csrf cookie - 再次在后端,将
@csrf_protect
装饰器添加到主视图中。这个确实导致csrf cookie被设置,尽管我不太明白为什么。无论如何,这并没有解决问题 - 在前端,将一个
csrfmiddlewaretoken
字段添加到POST请求的主体中,并将令牌作为值 - 再次在前端,获取Django文档中显示的具有完全相同功能的令牌,而不是我的自定义令牌
不幸的是,这些都没有改变结果:当我有可用的cookie时,它的值似乎不能满足Django。
我发现了问题:我的设置中的CSRF_HEADER_NAME = "X-CSRFToken"
没有考虑到Django的事实;"显式优于隐式";,隐式地规范化所有标头名称,这样请求中的令牌最终会看起来像HTTP_X_CSRFTOKEN
,但不会对自定义名称进行同样的处理,因此当Django比较它们时,两者不会匹配。