AngularJS + Django Rest Framework + CORS (файл cookie CSRF не отображается в клиенте)

Я занимаюсь разработкой одностраничного приложения на AngularJS с использованием Django Rest Framework + Django CORS Headers.

Моя проблема заключается в том, что файл cookie csrftoken никогда не появляется в моем браузере, когда я связывался с бэкэндом.

Например: я делаю логин используя пост. Я правильно получаю cookie "sessionid", но "csrftoken" никогда не появляется, и поэтому я не могу делать правильные сообщения от моего клиента, так как мне будет отказано из-за отсутствия токена csrf.

  • Я проанализировал заголовки ответа от API, а csrftoken - нет.
  • Я посмотрел прямо в браузере остальных API, и он хорошо там выглядит.
  • Просто чтобы указать, я могу сделать свой первый POST для входа в систему, так как Django Rest Framework вызывает только CSRF для аутентифицированных пользователей. Если я попытаюсь повторно войти в систему, это потерпит неудачу, так как "sessionid"-cookie это присутствует.
  • Я не заинтересован в том, чтобы обойти защиту CSRF, как предполагают некоторые посты по stackru.

Некоторые фрагменты кода от front/backend. Это незаконченные фрагменты, так что не зацикливайтесь на плохо написанном коде.

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)

Клиентский контроллер AngularJS

.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 = '';
    });
};

}]);

Кто-нибудь с хорошими советами / идеями / примерами?

5 ответов

Решение

Так что я нашел свое собственное решение, похоже, отлично работает.

Это новые фрагменты моего кода:

Backend 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", чтобы разрешить такой заголовок от клиента к API, использующему CORS, иначе публикация будет отклонена.

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

)

Это оно!

Одностраничное веб-приложение AngularJS на поддомене A, взаимодействующее с API-интерфейсом Django JSON (REST) ​​на поддомене B с использованием защиты CORS и CSRF

Поскольку в настоящее время я работаю над подобной настройкой и боролся за то, чтобы CORS работал правильно в сочетании с защитой CSRF, я хотел бы поделиться своими собственными знаниями здесь.

Настройка - SPA и API находятся в разных поддоменах одного домена:

  • AngularJS (1.2.14) одностраничное веб-приложение на поддомене app.mydomain.com
  • Приложение Django (1.6.2) реализует JSON REST API на поддомене api.mydomain.com

Приложение AngularJS обслуживается через приложение Django в том же проекте, что и приложение Django API, так что оно устанавливает файл cookie CSRF. Смотрите, например, также Как запустить несколько веб-сайтов из одного проекта Django

Приложение Django API - для обеспечения защиты CORS и CSRF мне нужно было сделать следующее в бэкэнде API.

В файле settings.py для этого приложения (расширение проекта settings.py в Django):

  • Добавьте приложение Corsheaders и промежуточное программное обеспечение, а также промежуточное программное обеспечение CSRF:
INSTALLED_APPS = (
    ...
    'corsheaders',
    ...
)

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

Также смотрите заголовки Django CORS на GitHub

  • Добавьте домен для веб-приложения SPA в CORS_ORIGIN_WHITELIST
CORS_ORIGIN_WHITELIST = [
    ...
    'app.mydomain.com',
    ...
]
  • Установите для CORS_ALLOW_CREDENTIALS значение True. Это важно, если вы этого не сделаете, файл cookie CSRF не будет отправлен с запросом

CORS_ALLOW_CREDENTIALS = True

Добавьте в ваши представления декоратор sure_csrf_cookie, обрабатывающий запросы JSON API:

from django.views.decorators.csrf import ensure_csrf_cookie

@ensure_csrf_cookie
def myResource(request):
    ...

Приложение Django для AngularJS - приложение AngularJS обслуживается через приложение Django в том же проекте. Это приложение Django настроено для установки файла cookie CSRF. Маркер CSRF из файла cookie затем используется для запросов к API (который, таким образом, выполняется как часть того же проекта Django).

Обратите внимание, что почти все файлы, связанные с приложением AngularJS, являются просто статическими файлами с точки зрения Django. Приложение Django должно только служить index.html, чтобы установить cookie.

В файле settings.py для этого приложения (снова расширение проекта settings.py в Django) установите CSRF_COOKIE_DOMAIN так, чтобы их могли использовать поддомены:

CSRF_COOKIE_DOMAIN = ".mydomain.com"

В views.py мне нужно только визуализировать файл AngularJS index.html, снова используя декоратор sure_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')

Отправка запросов в API с помощью AngularJS - в конфигурации приложения AngularJS установите следующие значения по умолчанию $httpProvider:

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

Опять же, обратите внимание на withCredentials, это гарантирует, что в запросе используется файл cookie CSRF.

Ниже я покажу, как вы можете делать запросы к API с помощью сервиса AngularJS $http и JQuery:

$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
    }
});

Надеюсь, это поможет!!

Непосредственно из документов https://docs.djangoproject.com/en/1.9/ref/csrf/

Если ваше представление не отображает шаблон, содержащий тег шаблона csrf_token, Django может не установить cookie-токен CSRF. Это часто встречается в тех случаях, когда формы динамически добавляются на страницу. Для решения этой проблемы Django предоставляет декоратор представления, который принудительно устанавливает настройку cookie: sure_csrf_cookie().

Поскольку ваше приложение является одностраничным, вы можете добавить ensure_csrf_cookie() к представлению, которое отвечает за начальную загрузку страницы.

Небольшое обновление к этому решению.

Начиная с AngularJS 1.2.10 вам необходимо установить файл cookie CSRF для каждого типа запроса в клиенте:

$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

Надеюсь, это поможет кому-то!

После столь длительного поиска я нашел это решение и его работу со мной в локальной системе, а также на живом веб-сервере фракций. Это мое решение для пользователей Django. Перейдите в папку apache, расположенную в проекте, а затем в бен, который вы найдете.

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> 

тогда в угловом приложении 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

Другие вопросы по тегам