私は ドキュメント とSOに関するすべての関連する質問を読みましたが、それでもAngularのXSRFメカニズムは機能しません:決してPOST X-XSRF-TOKENヘッダーが自動的に追加されたリクエスト。
Angular 6アプリにログインフォームがあります。
Symfony(PHP 7.1)Webサイトの一部であり、Angularアプリページは、Symfonyから提供されると、正しいCookie(XSRF-TOKEN
):
私の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.http
はHttpClient
のインスタンスです):
this.http
.post<any>('api/login', {'_username': username, '_pass': password})
.subscribe(/* handler here */);
投稿リクエストはX-XSRF-TOKENヘッダーを送信しません。どうして?
問題は、Angularの不十分なドキュメントです。
実際には、AngularはX-XSRF-TOKEN
ヘッダーifXSRF-TOKEN
Cookieは、次のオプションを使用してサーバー側で生成されました。
/
false
(これは非常に重要であり、完全にundocumented)また、Angularアプリと呼び出されるURLは同じサーバーに存在する必要があります。
リファレンス: this Angular Github issue
少し話題から外れていますが、ここに来る他の人のために、バックエンドでこの問題を次のように解決しました(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;
}
サーバーでX-CSRF-Token
ヘッダーの ブラウザリクエストOPTIONS
メソッド 。
例:
Access-Control-Allow-Headers: X-CSRF-Token, Content-Type
リファレンス: MDN Docs
AngularのHttpClientXsrfModule
は、CookieからXSRFトークンを読み取ります。これは次のことを意味します。
HttpOnly
の場合、AngularはJavascriptを介してアクセスできません。これは、すべてのWebアプリケーションに適用されます。また:
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';