36

I am developing a 1-page application in AngularJS using and Django Rest Framework + Django CORS Headers.

My problem is that the "csrftoken" cookie never shows up in my browser when I have contacted the backend.

For example: I am doing a login using a post. I get the "sessionid" cookie properly but the "csrftoken" never shows up and therefor I cannot do proper posts from my client since I will get denied due the lack of the csrf token.

  • I have analyzed the response headers from the API and the csrftoken is not ther.
  • I have looked directly in the rest API browser and it shows up fine there.
  • Just to point out, I can do my first POST to login since Django Rest Framework only forces CSRF for authenticated users. If I try to relogin it will fail since the "sessionid"-cookie it present.
  • I am not interessted in bypassing the CSRF protection as some posts on stackoverflow suggests.

Some code snippets from front/backend. These are unfinnished snippets, so dont get hung up on poorly written code.

Backend API LoginView

class LoginView(APIView):

renderer_classes = (JSONPRenderer, JSONRenderer)

def post(self, request, format=None):
    serializer = LoginSerializer(data=request.DATA)

    if serializer.is_valid():
        userAuth = authenticate(username=serializer.data['username'], password=serializer.data['password'])

        if userAuth:

            if userAuth.is_active:
                login(request, userAuth)

                loggedInUser = AuthUserProfile.objects.get(pk=1)
                serializer = UserProfileSerializer(loggedInUser)

                user = [serializer.data, {'isLogged': True}]



        else:
            user = {'isLogged': False}

        return Response(user, status=status.HTTP_200_OK)

    return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

Client side AngularJS Login Controller

.controller('LoginCtrl', ['$scope', '$http', 'uService', '$rootScope', function(scope, $http, User, rootScope) {

scope.login = function() {

    var config = {
        method: 'POST',
        withCredentials: true,
        url: rootScope.apiURL+'/user/login/',
        data : scope.loginForm
    };

    $http(config)
    .success(function(data, status, headers, config) {

        if (status == 200) {
            console.log(data[0]); //Test code
            // succefull login
            User.isLogged = true;
            User.username = data.username;

        }
        else {
            console.log(data); //Test code
            User.isLogged = false;
            User.username = '';
        }

    })
    .error(function(data, status, headers, config) {
        console.log('Testing console error');
        User.isLogged = false;
        User.username = '';
    });
};

}]);

Anyone with any good tips/ideas/examples?

4

5 回答 5

37

子域 A 上的 AngularJS 单页 Web 应用程序,使用 CORS 和 CSRF 保护与子域 B 上的 Django JSON (REST) API 对话

由于我目前正在使用类似的设置,并且正在努力让 CORS 与 CSRF 保护结合起来正常工作,所以我想在这里分享我自己的学习经验。

设置- SPA 和 API 都位于同一域的不同子域上:

  • AngularJS (1.2.14) 子域 app.mydomain.com 上的单页 Web 应用程序
  • Django App (1.6.2) 在子域 api.mydomain.com 上实现 JSON REST API

AngularJS 应用程序通过与 Django API APP 相同的项目中的 Django 应用程序提供服务,因此它设置了一个 CSRF Coo​​kie。例如,另请参阅如何从一个 Django 项目运行多个网站

Django API App - 为了让 CORS 和 CSRF 保护正常工作,我需要在 API 后端执行以下操作。

在此应用程序的 settings.py 中(Django 项目 settings.py 的扩展):

  • 添加 corsheaders 应用程序和中间件以及 CSRF 中间件:
INSTALLED_APPS = (
    ...
    'corsheaders',
    ...
)

MIDDLEWARE_CLASSES = (
    ...
    'django.middleware.csrf.CsrfViewMiddleware',
    ...
    'corsheaders.middleware.CorsMiddleware',
)

另请参阅GitHub 上的 Django CORS 标头

  • 将 SPA Webapp 的域添加到 CORS_ORIGIN_WHITELIST
CORS_ORIGIN_WHITELIST = [
    ...
    'app.mydomain.com',
    ...
]
  • 将 CORS_ALLOW_CREDENTIALS 设置为 True。这很重要,如果您不这样做,则不会随请求发送 CSRF cookie

CORS_ALLOW_CREDENTIALS = 真

将 ensure_csrf_cookie 装饰器添加到处理 JSON API 请求的视图中:

from django.views.decorators.csrf import ensure_csrf_cookie

@ensure_csrf_cookie
def myResource(request):
    ...

AngularJS 的Django 应用程序- AngularJS 应用程序通过同一项目中的 Django 应用程序提供服务。这个 Django 应用程序设置为设置 CSRF Coo​​kie。然后,cookie 中的 CSRF 令牌用于对 API 的请求(因此作为同一 Django 项目的一部分运行)。

请注意,几乎所有与 AngularJS 应用程序相关的文件都只是 Django 角度的静态文件。Django App 只需要提供 index.html 来设置 cookie。

在此应用程序的 settings.py(同样是 Django 项目 settings.py 的扩展)中,设置 CSRF_COOKIE_DOMAIN 以便子域也可以使用它们:

CSRF_COOKIE_DOMAIN = ".mydomain.com"

在views.py 中,我只需要渲染AngularJS index.html 文件,再次使用ensure_csrf_cookie 装饰器:

from django.shortcuts import render
from django.views.decorators.csrf import ensure_csrf_cookie

# Create your views here.
@ensure_csrf_cookie
def index(request):
    return render(request, 'index.html')

使用 AngularJS 向 API 发送请求- 在 AngularJS 应用配置中设置以下 $httpProvider 默认值:

$httpProvider.defaults.xsrfCookieName = 'csrftoken';
$httpProvider.defaults.xsrfHeaderName = 'X-CSRFToken';
$httpProvider.defaults.withCredentials = true;

再次注意 withCredentials,这可以确保在请求中使用 CSRF Coo​​kie。

下面我将展示如何使用 AngularJS $http 服务和 JQuery 向 api 发出请求:

$http.post("http://api.mydomain.com/myresource", {
    field1   : ...,
      ...
    fieldN   : ...
}, {
    headers : {
        "x-csrftoken" : $cookies.csrftoken
    }
});

另请参阅ngCookies 模块

使用 JQuery (1.11.0):

$.ajax("http://api.mydomain.com/myresource", {
    type: 'POST',
    dataType : 'json',
    beforeSend : function(jqXHR, settings) {
        jqXHR.setRequestHeader("x-csrftoken", get_the_csrf_token_from_cookie());
    },
    cache : false,
    contentType   : "application/json; charset=UTF-8",
    data : JSON.stringify({
        field1   : ...,
          ...
        fieldN   : ...
    }),
    xhrFields: {
        withCredentials: true
    }
});

我希望这有帮助!!

于 2014-03-22T22:13:44.827 回答
15

直接来自文档https://docs.djangoproject.com/en/1.9/ref/csrf/#ajax

如果您的视图未呈现包含 csrf_token 模板标签的模板,则 Django 可能不会设置 CSRF 令牌 cookie。这在表单被动态添加到页面的情况下很常见。为了解决这种情况,Django 提供了一个视图装饰器来强制设置 cookie:ensure_csrf_cookie()。

由于您的应用程序是单页应用程序,因此您可以添加ensure_csrf_cookie()到负责初始页面加载的视图中。

于 2013-07-30T04:03:45.637 回答
7

所以我找到了自己的解决方案,似乎效果很好。

这是我的代码的新片段:

后端 API LoginView(添加了一个装饰器,强制将 csrf 令牌添加到正文中)

class LoginView(APIView):

renderer_classes = (JSONPRenderer, JSONRenderer)

@method_decorator(ensure_csrf_cookie)
def post(self, request, format=None):
    c = {}
    c.update(csrf(request))
    serializer = LoginSerializer(data=request.DATA)

    if serializer.is_valid():
        userAuth = authenticate(username=serializer.data['username'], password=serializer.data['password'])

        if userAuth:

            if userAuth.is_active:
                login(request, userAuth)

                loggedInUser = AuthUserProfile.objects.get(pk=1)
                serializer = UserProfileSerializer(loggedInUser)

                user = [serializer.data, {'isLogged': True}]



        else:
            user = {'isLogged': False}

        return Response(user, status=status.HTTP_200_OK)

    return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

AngularJS 客户端(将令牌添加到请求标头)

$http.defaults.headers.post['X-CSRFToken'] = $cookies.csrftoken;

服务器端设置文件(专门用于 django-cors-headers )

默认添加前 5 个,但您需要添加“X-CSRFToken”以允许使用 CORS 从客户端到 API 的此类标头,否则该帖子将被拒绝。

CORS_ALLOW_HEADERS = (
'x-requested-with',
'content-type',
'accept',
'origin',
'authorization',
'X-CSRFToken'

)

而已!

于 2013-07-30T07:41:25.900 回答
6

对此解决方案的一个小更新。

从 AngularJS 1.2.10 开始,您需要为客户端中的每种请求类型设置 CSRF cookie:

$http.defaults.headers.post['X-CSRFToken'] = $cookies.csrftoken;
$http.defaults.headers.put['X-CSRFToken'] = $cookies.csrftoken;
$http.defaults.headers['delete']['X-CSRFToken'] = $cookies.csrftoken;

这是由于在 1.2.9 和 1.2.10 之间发生了以下变化 https://github.com/cironunes/angular.js/commit/781287473bc2e8ee67078c05b76242124dd43376

希望这对某人有帮助!

于 2014-03-02T19:56:12.577 回答
2

在如此多的搜索之后,我在本地系统和实时网络派系服务器上找到了这个解决方案及其工作形式,这是我为Django用户提供的解决方案,请转到位于项目中的 apache 文件夹,然后在 bin 中找到

httpd.conf 或您的 php 或其他用户的服务器配置(通常位于 *.conf 文件中,例如 httpd.conf 或 apache.conf),或在 .htaccess 中。然后只需添加此代码

<IfModule mod_headers.c>
SetEnvIf Origin (.*) AccessControlAllowOrigin=$1
Header add Access-Control-Allow-Origin %{AccessControlAllowOrigin}e env=AccessControlAllowOrigin
Header set Access-Control-Allow-Credentials true
</IfModule> 

然后在Angular js 应用程序中,您只需要放置

angular.module('app', ['ngCookies'])
    .config([
   '$httpProvider',
   '$interpolateProvider',
   function($httpProvider, $interpolateProvider, $scope, $http) {
       $httpProvider.defaults.withCredentials = true;
       $httpProvider.defaults.xsrfCookieName = 'csrftoken';
       $httpProvider.defaults.headers.post['Content-Type'] = 'application/x-www-form-urlencoded';
   }]).
   run([
   '$http',
   '$cookies',
   function($http, $cookies) {
       $http.defaults.headers.post['X-CSRFToken'] = $cookies.csrftoken;
   }]);

它在 Django Angularjs 平台上为我工作。

https://gist.github.com/mlynch/be92735ce4c547bd45f6

于 2017-02-06T04:29:36.963 回答