Angular 5 无法从 HttpXsrfTokenExtractor 获取 XSRF 令牌
Angular 5 unable to get XSRF token from HttpXsrfTokenExtractor
我正在尝试通过 绝对 URL 向 Spring(基本身份验证)安全 Rest API.
读到 Angular 省略了将 X-XSRF-TOKEN 自动插入请求 header 后,我尝试实现一个 HttpInterceptor 添加令牌。
在我最初的 /signin POST 请求中,我创建了必要的 authorization: Basic header 以确保 Spring 对请求进行身份验证。
响应 header returned 包含预期的 set-cookie 标记:
Set-Cookie:XSRF-TOKEN=4e4a087b-4184-43de-81b0-e37ef953d755; Path=/
但是,在我的自定义拦截器 class 中,当我尝试从注入的 HttpXsrfTokenExtractor 获取令牌以用于下一个请求时,它 returns null .
这是我的拦截器的代码 class:
import {Injectable, Inject} from '@angular/core';
import { Observable } from 'rxjs/Rx';
import {HttpInterceptor, HttpXsrfTokenExtractor, HttpRequest, HttpHandler,
HttpEvent} from '@angular/common/http';
@Injectable()
export class HttpXsrfInterceptor implements HttpInterceptor {
constructor(private tokenExtractor: HttpXsrfTokenExtractor) {}
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
let requestMethod: string = req.method;
requestMethod = requestMethod.toLowerCase();
if (requestMethod && (requestMethod === 'post' || requestMethod === 'delete' || requestMethod === 'put' )) {
const headerName = 'X-XSRF-TOKEN';
let token = this.tokenExtractor.getToken() as string;
if (token !== null && !req.headers.has(headerName)) {
req = req.clone({ headers: req.headers.set(headerName, token) });
}
}
return next.handle(req);
}
}
上面代码中的tokenExtractor.getToken() returns null。我希望它 return 来自 Spring (Set-Cookie) 响应 header 的令牌,我之前的 /signin 请求。
我阅读了有关创建拦截器的相关 post:
但除此之外,我没能找到很多关于 HttpXsrfTokenExtractor 的文档:
https://angular.io/api/common/http/HttpXsrfTokenExtractor
问题:为什么 HttpXsrfTokenExtractor.getToken() return 为空?
此外,我添加了拦截器 class 作为 app.module.
的提供者
这是我的 app.module.ts:
import { NgModule, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
import { LocationStrategy, HashLocationStrategy, APP_BASE_HREF } from '@angular/common';
import { BrowserModule } from '@angular/platform-browser';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { HttpClientModule, HttpClientXsrfModule } from '@angular/common/http';
import { HTTP_INTERCEPTORS } from '@angular/common/http';
import { AlertModule } from 'ng2-bootstrap';
import { routing, appRouterProviders } from './app.routing';
import { HttpXsrfInterceptor } from './httpxrsf.interceptor';
import { AppComponent } from './app.component';
import { LoginComponent } from './login/login.component';
import { RegisterComponent } from './registration/register.component';
import { HomeComponent } from './home/home.component';
@NgModule({
declarations: [AppComponent,
LoginComponent,
RegisterComponent,
HomeComponent],
imports: [BrowserModule,
FormsModule,
ReactiveFormsModule,
HttpClientModule,
HttpClientXsrfModule, // Adds xsrf support
AlertModule.forRoot(),
routing],
schemas: [CUSTOM_ELEMENTS_SCHEMA],
providers: [
appRouterProviders,
[{provide: APP_BASE_HREF, useValue: '/'}],
[{provide: LocationStrategy, useClass: HashLocationStrategy}],
[{provide: HTTP_INTERCEPTORS, useClass: HttpXsrfInterceptor, multi: true }]
],
bootstrap: [AppComponent]
})
export class AppModule {}
另一件需要注意的事情是我 运行 我的 Angular 前端来自 localhost:3000 Node.js 和我的 Spring 休息 back-end 来自 localhost:8080。
它们在不同的端口上,因此使用 绝对 urls 发出 http 请求的原因。当 Set-Cookie 来自对不同域上的请求的响应时,浏览器会阻止它工作吗?
我还能错过什么吗?
感谢您的帮助。
-------------------------------------------- -
[2018 年 1 月 7 日更新]
FIX
我使用 Webpack dev server 来提供 Angular 代码。我通过为指向我的 back-end Rest API.
的网址配置 proxy 来解决这个问题
这意味着从浏览器发出的所有请求现在只能发送到端口 3000 的开发服务器,即使是 Rest API 调用也是如此。
当 webpack dev server 看到任何具有配置模式的请求 url(例如 /api/...),它会替换为对 back-end 服务器的调用http://localhost:8080(以我为例)。
这是我添加到 webpack.dev.js 文件的 devServer 部分的内容:
proxy: {
'/api': {
'target': 'http://localhost:8080',
'pathRewrite': {'^/api' : ''}
//Omits /api from the actual request. E.g. http://localhost:8080/api/adduser -> http://localhost:8080/adduser
}
}
通过此设置,我不再从 Angular 代码发出 cross-domain (cross-origin) 请求,也不再使用绝对 URLs。这极大地简化了事情,因为我不再与 Angular XSRF (CSRF) 机制作斗争。它只是默认工作。我也不需要使用 HttpInterceptor 手动插入 X-XSRF-TOKEN。
设置开发服务器代理的额外好处是客户端请求不再是绝对的,因此我不需要更改所有 Rest API 生产调用。
我希望这对任何遭受同样痛苦的人都有用 problem/understanding。
Webpack 开发服务器代理文档参考:
https://webpack.js.org/configuration/dev-server/#devserver-proxy
由于您使用不同的端口(3000 & 8080),您正在发出跨域请求,因此您将无法读取服务器发送的客户端中的cookie。如果要以这种方式分离客户端和服务器,则需要使用代理,以便客户端和服务器应用程序从相同的协议 (http/https)、域和端口提供服务。如果您正在使用 Spring Boot,我建议您查看 Spring Cloud Netflix,特别是 Zuul (https://cloud.spring.io/spring-cloud-netflix/single/spring-cloud-netflix.html#_router_and_filter_zuul)。
我正在尝试通过 绝对 URL 向 Spring(基本身份验证)安全 Rest API.
读到 Angular 省略了将 X-XSRF-TOKEN 自动插入请求 header 后,我尝试实现一个 HttpInterceptor 添加令牌。
在我最初的 /signin POST 请求中,我创建了必要的 authorization: Basic header 以确保 Spring 对请求进行身份验证。
响应 header returned 包含预期的 set-cookie 标记:
Set-Cookie:XSRF-TOKEN=4e4a087b-4184-43de-81b0-e37ef953d755; Path=/
但是,在我的自定义拦截器 class 中,当我尝试从注入的 HttpXsrfTokenExtractor 获取令牌以用于下一个请求时,它 returns null .
这是我的拦截器的代码 class:
import {Injectable, Inject} from '@angular/core';
import { Observable } from 'rxjs/Rx';
import {HttpInterceptor, HttpXsrfTokenExtractor, HttpRequest, HttpHandler,
HttpEvent} from '@angular/common/http';
@Injectable()
export class HttpXsrfInterceptor implements HttpInterceptor {
constructor(private tokenExtractor: HttpXsrfTokenExtractor) {}
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
let requestMethod: string = req.method;
requestMethod = requestMethod.toLowerCase();
if (requestMethod && (requestMethod === 'post' || requestMethod === 'delete' || requestMethod === 'put' )) {
const headerName = 'X-XSRF-TOKEN';
let token = this.tokenExtractor.getToken() as string;
if (token !== null && !req.headers.has(headerName)) {
req = req.clone({ headers: req.headers.set(headerName, token) });
}
}
return next.handle(req);
}
}
上面代码中的tokenExtractor.getToken() returns null。我希望它 return 来自 Spring (Set-Cookie) 响应 header 的令牌,我之前的 /signin 请求。
我阅读了有关创建拦截器的相关 post:
但除此之外,我没能找到很多关于 HttpXsrfTokenExtractor 的文档:
https://angular.io/api/common/http/HttpXsrfTokenExtractor
问题:为什么 HttpXsrfTokenExtractor.getToken() return 为空?
此外,我添加了拦截器 class 作为 app.module.
的提供者
这是我的 app.module.ts:
import { NgModule, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
import { LocationStrategy, HashLocationStrategy, APP_BASE_HREF } from '@angular/common';
import { BrowserModule } from '@angular/platform-browser';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { HttpClientModule, HttpClientXsrfModule } from '@angular/common/http';
import { HTTP_INTERCEPTORS } from '@angular/common/http';
import { AlertModule } from 'ng2-bootstrap';
import { routing, appRouterProviders } from './app.routing';
import { HttpXsrfInterceptor } from './httpxrsf.interceptor';
import { AppComponent } from './app.component';
import { LoginComponent } from './login/login.component';
import { RegisterComponent } from './registration/register.component';
import { HomeComponent } from './home/home.component';
@NgModule({
declarations: [AppComponent,
LoginComponent,
RegisterComponent,
HomeComponent],
imports: [BrowserModule,
FormsModule,
ReactiveFormsModule,
HttpClientModule,
HttpClientXsrfModule, // Adds xsrf support
AlertModule.forRoot(),
routing],
schemas: [CUSTOM_ELEMENTS_SCHEMA],
providers: [
appRouterProviders,
[{provide: APP_BASE_HREF, useValue: '/'}],
[{provide: LocationStrategy, useClass: HashLocationStrategy}],
[{provide: HTTP_INTERCEPTORS, useClass: HttpXsrfInterceptor, multi: true }]
],
bootstrap: [AppComponent]
})
export class AppModule {}
另一件需要注意的事情是我 运行 我的 Angular 前端来自 localhost:3000 Node.js 和我的 Spring 休息 back-end 来自 localhost:8080。
它们在不同的端口上,因此使用 绝对 urls 发出 http 请求的原因。当 Set-Cookie 来自对不同域上的请求的响应时,浏览器会阻止它工作吗?
我还能错过什么吗?
感谢您的帮助。
-------------------------------------------- -
[2018 年 1 月 7 日更新]
FIX
我使用 Webpack dev server 来提供 Angular 代码。我通过为指向我的 back-end Rest API.
的网址配置 proxy 来解决这个问题
这意味着从浏览器发出的所有请求现在只能发送到端口 3000 的开发服务器,即使是 Rest API 调用也是如此。
当 webpack dev server 看到任何具有配置模式的请求 url(例如 /api/...),它会替换为对 back-end 服务器的调用http://localhost:8080(以我为例)。
这是我添加到 webpack.dev.js 文件的 devServer 部分的内容:
proxy: {
'/api': {
'target': 'http://localhost:8080',
'pathRewrite': {'^/api' : ''}
//Omits /api from the actual request. E.g. http://localhost:8080/api/adduser -> http://localhost:8080/adduser
}
}
通过此设置,我不再从 Angular 代码发出 cross-domain (cross-origin) 请求,也不再使用绝对 URLs。这极大地简化了事情,因为我不再与 Angular XSRF (CSRF) 机制作斗争。它只是默认工作。我也不需要使用 HttpInterceptor 手动插入 X-XSRF-TOKEN。
设置开发服务器代理的额外好处是客户端请求不再是绝对的,因此我不需要更改所有 Rest API 生产调用。
我希望这对任何遭受同样痛苦的人都有用 problem/understanding。
Webpack 开发服务器代理文档参考:
https://webpack.js.org/configuration/dev-server/#devserver-proxy
由于您使用不同的端口(3000 & 8080),您正在发出跨域请求,因此您将无法读取服务器发送的客户端中的cookie。如果要以这种方式分离客户端和服务器,则需要使用代理,以便客户端和服务器应用程序从相同的协议 (http/https)、域和端口提供服务。如果您正在使用 Spring Boot,我建议您查看 Spring Cloud Netflix,特别是 Zuul (https://cloud.spring.io/spring-cloud-netflix/single/spring-cloud-netflix.html#_router_and_filter_zuul)。