Angular2 和 Django:CSRF 令牌头痛

Posted

技术标签:

【中文标题】Angular2 和 Django:CSRF 令牌头痛【英文标题】:Angular2 and Django: CSRF Token Headache 【发布时间】:2017-01-18 18:34:36 【问题描述】:

我遇到的问题

我正在从我的 Angular2 客户端向我的 Django (v1.9) 后端(都在本地主机上,不同的端口)发出 Ajax POST 请求。我还没有使用 Django REST 框架,我只是在没有任何附加组件的情况下将 JSON 转储到 Django 中。 服务器已向我发出 csrf 令牌,我正在手动将其发送回 HTTP 标头(我在拨打电话时可以看到它)。 但是,我仍然收到来自 django 的错误:Forbidden (CSRF cookie not set.) 我已经阅读了许多其他线程,并尝试了一些方法,但仍然无法让 Django 接受 CSRF 令牌。

客户端代码:

private _editUserUri = "http://127.0.0.1:8000/search/edit/user/";

constructor(private _http: Http)

getCookie(name) 
    let value = "; " + document.cookie;
    let parts = value.split("; " + name + "=");
    if (parts.length == 2) 
      return parts.pop().split(";").shift();


validateData(userdata) 
    return true;


editUser(userdata) 

    console.log("UserService: createUser function called");
    console.log(JSON.stringify(userdata));

    if(this.validateData(userdata)) 

        let headers = new Headers(
            'Content-Type': 'application/json',
            'X-CSRFToken': this.getCookie('csrftoken')
        );

        let options = new RequestOptions( headers: headers );

        return this._http
            .post(
                this._editUserUri,
                JSON.stringify(userdata),
                options)
            .map(res => 
                console.log(res.json());
                return res.json();
            )
    


标头 (x-csrftoken) 中的 csrf 令牌的屏幕截图。这正是 Django 所期望的 (source)

我尝试过/考虑过的事情

我认为可能是 csrf 令牌已过时。我试图生成一个新的,但我还没有设法让 Django 给我一个新的。我试过使用 @ensure_csrf_cookie 装饰器,但仍然没有。如果我今天早上放置的 cookie 中有一个 csrf 令牌,我是否希望它被一个新的、最新的版本替换?

另一个想法是标题名称 (x-csrftoken) 是小写的。 Django 指定标头名称应如下所示:X-CSRFToken。但是,从在线阅读看来,标题通常应该区分大小写。在任何情况下,Angular2 都会自动将标题名称转换为小写。

我尝试了一些在 Stack Overflow (example) 上找到的其他解决方案,但没有成功。

我认为这可能与 CORS 有关,但第一个 OPTIONS 请求从我的 Django 服务器返回 200。此飞行前请求也不需要在其标头中包含 csrf 令牌,对吗? (原谅我的无知)

我很确定我的设置没问题。我在 MIDDLEWARE_CLASSES 中有 'django.middleware.csrf.CsrfViewMiddleware',CSRF_COOKIE_SECURE = FalseCORS_ALLOW_CREDENTIALS = TrueCORS_ORIGIN_ALLOW_ALL = True

如果有人能提供帮助,我将不胜感激!

尼克

【问题讨论】:

您可以尝试在csrfmiddlewaretoken POST 参数中传递令牌。不确定标头名称是否区分大小写,但您可以通过不将其转换为小写的东西(可能是 jquery)发出发布请求来检查它。 【参考方案1】:

我认为问题在于您的请求只有 CSRF 令牌标头,但没有 cookie(请参阅double submit 针对 CSRF 的缓解措施,您发送的标头应与 cookie 进行比较)。

我不太明白到底发生了什么。如果您详细说明 Angular 应用程序的下载位置以及它如何从不同来源的 Django 获取 CSRF cookie,我可能可以编辑此答案以提供更多帮助。我不明白的是,如果 Angular 应用程序是从端口 A 下载的,它从哪里有一个用于端口 B 上的 Django 的 CSRF cookie。

编辑:

啊,您正在使用 CORS。我认为您在 jQuery 请求中需要 withCredentials 以便随请求一起发送 csrf cookie:

xhrFields: 
    withCredentials: true

然后Django需要发送Access-Control-Allow-Credentials: true我想。

【讨论】:

嗨 lengyelg,感谢您的回复。我在我的本地主机上运行两台服务器:一台在 127.0.0.1:3000 (node.js angular2) 托管 angular 应用程序,另一台在 127.0.0.1:8000 (django)。当我转到 127.0.0.1:3000(或 localhost:3000)时,我可以在控制台 (document.cookie) 以及 Chrome -> Settings -> Content Settings -> Cookies (csrftoken=xr0Yc7umZdkXEO2dbRrnYNMcHTH4zauT) 中看到 cookie。我的 Angular 应用从此 cookie 获取令牌 ID。据我了解,Django 在响应 http 请求时设置了 cookie(如果存在 @ensure_csrf_cookie,则每次都应该这样做)。 是的,它不起作用的原因是我认为 CORS。您需要通过 1) 将 Access-Control-Allow-Credentials: true 添加到 Django 响应和 2) 设置 jQuery 以通过将 withCredentials 设置为 true 来发送带有跨域请求的 cookie,从而启用在跨域请求中发送 cookie。我认为这两个对发送 csrf cookie 有帮助,这一切都可以正常工作。 :) 感谢 lengyelg,我稍后再试一试,让您知道!由于我使用的是 Angular2 / typescript,我将尝试看看是否有一种内置的方式来完成第二部分(无需涉及 jQuery)。 修复了!当我检查网络选项卡时,Django 确实在每个响应中都向我发送了 cookie,但 Angular 并没有根据请求发送会话和 csrf cookie。我通过将 withCredentials: true 添加到我的 RequestOptions 对象来修复它。谢啦!! (由于我的声望低于 15,我无法为您的回复 +1,但已确认为正确答案)

以上是关于Angular2 和 Django:CSRF 令牌头痛的主要内容,如果未能解决你的问题,请参考以下文章

Rails 的 Angular 2 CSRF 令牌

Django - 在同一个模板(ajax 和表单)中处理多个 CSRF 令牌禁止(CSRF 令牌丢失或不正确。)

Angular 2 Spring Security CSRF 令牌

如何使用 Django 和 AngularJS 创建 POST 请求(包括 CSRF 令牌)

禁止(CSRF 令牌丢失或不正确。)Django

Django:CSRF 令牌丢失或不正确。 / 避免 % csrf_token %