Angular2和Django:CSRF令牌头疼

3

我遇到的问题

  • 我从我的Angular2客户端向我的Django(v1.9)后端发出了一个Ajax POST请求(两者都在本地主机上,不同的端口)。 我尚未使用Django REST框架,我只是在Django中转储JSON而没有任何附加组件。
  • 服务器已向我发出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();
            })
    }

}

在头部中的csrf token截图(x-csrftoken)。这正是Django所期望的 (来源) 截图

我尝试过/考虑过以下方法:

  • 我认为可能csrf token已经过期了。我尝试生成一个新的,但我还没有成功让Django发放给我一个新的。我尝试使用@ensure_csrf_cookie装饰器,但仍然没有用。如果我有一个csrf token在今天早上放置的cookie中,我是否应该期望它被一个新的、最新的版本替换?

  • 另一个想法是,头部名称(x-csrftoken)是小写的。Django指定头部名称应该是以下内容:X-CSRFToken。然而,从在线阅读中,似乎头部通常区分大小写。无论如何,Angular2自动将头部名称转换为小写。

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

  • 我认为这可能与CORS有关,但我的Django服务器从第一个OPTIONS请求返回200。这个预检请求不需要在它的头部中包含csrf token,对吗?(请原谅我的无知)

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

如果有人可以帮忙,我将非常感激!

Nick


你可以尝试将令牌传递到 csrfmiddlewaretoken POST 参数中。不确定标题名称是否区分大小写,但您可以通过使用不会将其转换为小写的工具(例如jquery)进行POST请求来检查它。 - serg
1个回答

4
我想问题在于你的请求只有CSRF令牌头,而没有cookie(参见双重提交防范CSRF,应该将你发送的头部与cookie进行比较)。
不过我不太明白具体发生了什么。如果你能详细解释一下Angular应用程序是从哪里下载的以及它如何从Django获取CSRF cookie(该cookie来自不同的源),我可能会编辑这个答案并提供更多帮助。我不理解的是,如果Angular应用程序是从端口A下载的,那么它是从哪里获取Django端口B的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->设置->内容设置->Cookie(csrftoken=xr0Yc7umZdkXEO2dbRrnYNMcHTH4zauT)中看到cookie。我的Angular应用程序从此cookie获取令牌ID。据我所知,Django在对http请求的响应中设置cookie(如果@ensure_csrf_cookie存在,则应该每次都设置)。 - Nicholas James Hall
是的,它无法工作的原因我认为是CORS。您需要通过以下两种方式启用跨源请求中发送cookie:1)在Django响应中添加Access-Control-Allow-Credentials: true;2)通过将withCredentials设置为true来设置jQuery以在跨源请求中发送cookie。 我认为这两个步骤可以帮助发送csrf cookie,并且一切都会正常工作。 :) - Gabor Lengyel
谢谢lengyelg,稍后我会尝试一下并告诉你结果!因为我正在使用Angular2/typescript,我会尝试看看是否有内置的方法来完成第二部分(而不必使用jQuery)。 - Nicholas James Hall
搞定了!当我检查网络选项卡时,发现Django确实在每个响应中发送了cookie,但Angular没有在请求中发送会话和csrf cookie。我通过将 { withCredentials: true } 添加到我的RequestOptions对象中来解决了这个问题。谢谢,伙计!(我不能+1您的回答,因为我的声望不足15分,但已确认为正确答案) - Nicholas James Hall

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接