Как правильно использовать http-запросы angular2 с защитой Django CSRF?

в Angular1 проблема может быть решена путем настройки $http-provider. Например:

app.config(function($httpProvider) {
  $httpProvider.defaults.xsrfCookieName = 'csrftoken';
  $httpProvider.defaults.xsrfHeaderName = 'X-CSRFToken';
});

что такое хорошая практика, чтобы сделать то же самое в Angular2?

в Angular2 для работы с http-запросами нам нужно использовать класс Http. Конечно, это не очень хорошая практика для добавления CSRF-line к каждому вызову post-функции.

Я думаю, что в Angular2 я должен создать собственный класс, который наследует Http-класс Angular2 и переопределить пост-функцию. Это правильный подход или есть более элегантный метод?

7 ответов


ответ Виктора к совершенно действителен, однако, с углового 2.0.0-rc.2, предпочтительным подходом было бы использовать CookieXSRFStrategy, как показано ниже,

bootstrap(AngularApp, [
  HTTP_PROVIDERS,
  provide(XSRFStrategy, {useValue: new CookieXSRFStrategy('csrftoken', 'X-CSRFToken')})
]);

теперь, когда Angular 2 выпущен, кажется, что это правильный способ сделать это, используя CookieXSRFStrategy.

Я настроил свое приложение, чтобы иметь базовый модуль но вы можете сделать то же самое в главном модуле приложения:

import { ModuleWithProviders, NgModule, Optional, SkipSelf } from '@angular/core';
import { CommonModule }   from '@angular/common';
import { HttpModule, XSRFStrategy, CookieXSRFStrategy } from '@angular/http';

@NgModule({
    imports: [
        CommonModule,
        HttpModule
     ],
    declarations: [ ],
    exports: [ ],
    providers: [
        {
            provide: XSRFStrategy,
            useValue: new CookieXSRFStrategy('csrftoken', 'X-CSRFToken')
        }
    ]
})


export class CoreModule {
}, 

решение для Angular2 не так просто, как для angular1. Вам нужно:

  1. выбрать csrftoken значение cookie.

  2. добавьте это значение для запроса заголовков с именем X-CSRFToken.

Я предлагаю этот фрагмент:

import {Injectable, provide} from 'angular2/core';
import {BaseRequestOptions, RequestOptions} from 'angular2/http'

@Injectable()
export class ExRequestOptions extends BaseRequestOptions  {
  constructor() {
    super();
    this.headers.append('X-CSRFToken', this.getCookie('csrftoken'));
  }

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

export var app = bootstrap(EnviromentComponent, [
  HTTP_PROVIDERS,
  provide(RequestOptions, {useClass: ExRequestOptions})
]);

для более поздних версий angular вы не можете вызывать функции в декораторах. Вы должны использовать поставщика фабрики:

export function xsrfFactory() {
  return new CookieXSRFStrategy('_csrf', 'XSRF-TOKEN');
}

и затем использовать фабрику:

  providers: [
    {
      provide: XSRFStrategy,
      useFactory : xsrfFactory
  }],

в противном случае компилятор будет выдавать. Я также видел, что ng build -- watch не сообщит об этой ошибке, пока вы не запустите ее снова.


У Виктора К было решение, я просто добавлю этот комментарий здесь о том, что я сделал:

Я создал компонент "ExRequestOptions", как сказал Виктор К, но я также добавил метод" appendHeaders " к этому компоненту:

appendHeaders(headername: string, headervalue: string) {
    this.headers.append(headername, headervalue);
}

тогда у меня было это в моей основной.ТС:

import {bootstrap}    from 'angular2/platform/browser'
import {AppComponent} from './app.component'
import {HTTP_PROVIDERS, RequestOptions} from 'angular2/http';
import 'rxjs/Rx';
import {ExRequestOptions} from './transportBoxes/exRequestOptions';
import {provide} from 'angular2/core';

bootstrap(AppComponent,[ HTTP_PROVIDERS,  
    provide(RequestOptions, {useClass: ExRequestOptions})]);

Я не уверен, что загрузка имела какой-либо эффект, поэтому я также сделал это, где Я бы опубликовал данные:

    let options = new ExRequestOptions();
    options.appendHeaders('Content-Type', 'application/json');
    return this.http.post('.....URL', JSON.stringify(registration),
         options)

я боролся с этим в течение нескольких дней. Советы в этой статье хороши, но по состоянию на август 2017 года устарели (https://github.com/angular/angular/pull/18906). Рекомендуемый подход angular2 прост, но имеет оговорку.

рекомендуемый подход заключается в использовании HttpClientXsrfModule и настроить его для распознавания защиты csrf django по умолчанию. По словам Джанго docs, django отправит cookie csrftoken и ожидайте, что клиент вернет заголовок X-CSRFToken. В angular2 добавьте к вашему app.module.ts

import { HttpClientModule, HttpClientXsrfModule } from '@angular/common/http';

@NgModule({
  imports: [
    HttpClientModule,
    HttpClientXsrfModule.withOptions({
      cookieName: 'csrftoken',
      headerName: 'X-CSRFToken',
    })
  ], ...

нюанс заключается в том, что angular2 это защиты XSRF применяется только к мутации запросы:

по умолчанию перехватчик отправляет этот файл cookie [заголовок] на все изменяющиеся запросы (POST, etc.) к относительным URL, но не на GET/HEAD запросов или на запросы с абсолютным URL.

Если вам нужно поддерживать API, который выполняет мутацию на GET/HEAD, вам нужно будет создать свой собственный перехватчик. Вы можете найти пример и обсуждение вопроса здесь.


В настоящее время я решаю что-либо с пользовательскими заголовками, используя службу-оболочку вокруг службы Http. Вы можете добавить любой заголовок вручную и ввести дополнительные службы для хранения/получения значений. Эта стратегия также работает для JWTs, например. Посмотрите на код ниже, я надеюсь, что это поможет.

import {Injectable} from '@angular/core';
import {Http, Headers, RequestOptions} from '@angular/http';

@Injectable()
export class HttpService {
  constructor(private http: Http) {
  }

  private get xsrfToken() {
    // todo: some logic to retrieve the cookie here. we're in a service, so you can inject anything you'd like for this
    return '';
  }

  get(url) {
    return this.http.get(url, this.getRequestOptions())
      .map(result => result.json())
      .catch(error => error.json());
  }

  post(url, payload) {
    return this.http.post(url, payload, this.getRequestOptions())
      .map(result => result.json())
      .catch(error => error.json());
  }

  private getRequestOptions() {
    const headers = new Headers({'Content-Type': 'application/json', 'X-XSRF-TOKEN': this.xsrfToken});
    return new RequestOptions({headers: headers});
  }
}