web-dev-qa-db-ja.com

Angular 6は、HTTP要求にX-XSRF-TOKENヘッダーを追加しません

私は ドキュメント とSOに関するすべての関連する質問を読みましたが、それでもAngularのXSRFメカニズムは機能しません:決してPOST X-XSRF-TOKENヘッダーが自動的に追加されたリクエスト。

Angular 6アプリにログインフォームがあります。

Symfony(PHP 7.1)Webサイトの一部であり、Angularアプリページは、Symfonyから提供されると、正しいCookie(XSRF-TOKEN):

enter image description here

私のapp.module.tsには適切なモジュールが含まれています。

// other imports...
import {HttpClientModule, HttpClientXsrfModule} from "@angular/common/http";

// ...
@NgModule({
  declarations: [
    // ...
  ],
  imports: [
    NgbModule.forRoot(),
    BrowserModule,
    // ...
    HttpClientModule,
    HttpClientXsrfModule.withOptions({
      cookieName: 'XSRF-TOKEN',
      headerName: 'X-CSRF-TOKEN'
    }),
    // other imports
  ],
  providers: [],
  entryComponents: [WarningDialog],
  bootstrap: [AppComponent]
})
export class AppModule {
}

次に、サービスのメソッド内で、次のhttpリクエストを作成しています(this.httpHttpClientのインスタンスです):

this.http
    .post<any>('api/login', {'_username': username, '_pass': password})
    .subscribe(/* handler here */);

投稿リクエストはX-XSRF-TOKENヘッダーを送信しません。どうして?

14
Paolo Stefan

問題は、Angularの不十分なドキュメントです。

実際には、AngularはX-XSRF-TOKENヘッダーifXSRF-TOKEN Cookieは、次のオプションを使用してサーバー側で生成されました。

  • パス= /
  • httpOnly = false(これは非常に重要であり、完全にundocumented

また、Angularアプリと呼び出されるURLは同じサーバーに存在する必要があります。

リファレンス: this Angular Github issue

26
Paolo Stefan

少し話題から外れていますが、ここに来る他の人のために、バックエンドでこの問題を次のように解決しました(spring-boot

     /**
     * CORS config - used by cors() in configure() DO NOT CHANGE the METDHO NAME
     * 
     * @return
     */
    @Bean()
    public CorsConfigurationSource corsConfigurationSource() {
        CorsConfiguration configuration = new CorsConfiguration();
        configuration.setAllowedOrigins(Lists.newArrayList("http://localhost:4200"));
        configuration.setAllowedMethods(Lists.newArrayList("GET", "POST", "OPTIONS"));
        configuration.setAllowCredentials(true);
        configuration.setAllowedHeaders(Lists.newArrayList("x-xsrf-token", "XSRF-TOKEN"));
        configuration.setMaxAge(10l);
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        source.registerCorsConfiguration("/**", configuration);
        return source;
    }
3
Anand Rockzz

サーバーでX-CSRF-Tokenヘッダーの ブラウザリクエストOPTIONSメソッド

例:

Access-Control-Allow-Headers: X-CSRF-Token, Content-Type

リファレンス: MDN Docs

2
Ritwick Dey

AngularのHttpClientXsrfModuleは、CookieからXSRFトークンを読み取ります。これは次のことを意味します。

  • バックエンドが別のドメインにある場合、AngularはCookie値を読み取ることができません。
  • HttpOnlyの場合、AngularはJavascriptを介してアクセスできません。

これは、すべてのWebアプリケーションに適用されます。また:

  • Angularは、絶対URLを持つリクエストにXSRFトークンを追加しないことを選択します( source を参照)。
  • Angularは、このヘッダーをGET/HEAD要求に追加しません。

条件は次のとおりです(lcURLは小文字のURLです):

if (req.method === 'GET' || req.method === 'HEAD' || lcUrl.startsWith('http://') ||
    lcUrl.startsWith('https://')) {
  return next.handle(req);
}

GET/HEADを除くすべてのリクエストにXSRFヘッダーを追加するために、AngularのHttpClientXsrfModule実装を取得して変更しました。

また、ブラウザーが{withCredentials: true}応答ヘッダーを適用するように、すべての要求にSet-Cookieを設定する必要がありました。

これが私の実装です(改善を提案することをheしないでください!):

import {HttpEvent, HttpHandler, HttpInterceptor, HttpRequest} from '@angular/common/http';
import {Injectable} from '@angular/core';
import {Observable} from 'rxjs';

/**
 * This interceptor reads the value from the cookie then adds it as a header of every request.
 *
 * Warning : some configuration is necessary on the *BACKEND* server :
 * 1) Set header Access-Control-Allow-Origin to ${frontend url}, for example http://localhost or https://www.mycompany.com)
 * 2) Set header Access-Control-Allow-Methods to GET,POST,OPTIONS,DELETE,PUT
 * This will allow the frontend to make POST/PUT/DELETE request to the backend.
 *
 * 3) Set header Access-Control-Allow-Headers to x-xsrf,content-type
 * This allows to add the frontend to add x-xsrf header in backend requests
 *
 * 4) Set header Access-Control-Allow-Credentials to true
 * This allows to expose authentication cookies to the frontend
 *
 * See :
 * - https://developer.mozilla.org/en/docs/Web/HTTP/Headers/Access-Control-Allow-Origin
 * - https://developer.mozilla.org/en/docs/Web/HTTP/Headers/Access-Control-Allow-Methods
 * - https://developer.mozilla.org/en/docs/Web/HTTP/Headers/Access-Control-Allow-Headers
 * - https://developer.mozilla.org/en/docs/Web/HTTP/Headers/Access-Control-Allow-Credentials
 */
@Injectable()
export class XsrfInterceptor implements HttpInterceptor {

  static readonly XSRF_HEADER_NAME = 'x-xsrf';
  static readonly XSRF_COOKIE_NAME = 'XSRF';

  intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
    const xsrfToken = this.getCookie(XsrfInterceptor.XSRF_COOKIE_NAME);
    if (xsrfToken && req.method !== 'GET' && req.method !== 'HEAD') {
      req = req.clone({headers: req.headers.set(XsrfInterceptor.XSRF_HEADER_NAME, xsrfToken)});
    }
    // Always withCredentials, so that 'set-cookie' is taken into account.
    req = req.clone({withCredentials: true});
    return next.handle(req);
  }

  /*
   * See https://stackoverflow.com/questions/10730362/get-cookie-by-name
   */
  getCookie(name): string {
    const value = '; ' + document.cookie;
    const parts = value.split('; ' + name + '=');
    if (parts.length === 2) { return parts.pop().split(';').shift(); }
  }
}

使用法

モジュールプロバイダーに追加するだけです。

providers: [
  // [...]
  { provide: HTTP_INTERCEPTORS, useClass: XsrfInterceptor, multi: true },
]

これらの値をカスタマイズすることもできます(クラスの最初):

static readonly XSRF_HEADER_NAME = 'x-xsrf';
static readonly XSRF_COOKIE_NAME = 'xsrf';
0