Angular 5 无法从 HttpXsrfTokenExtractor 获取 XSRF 令牌

Posted

技术标签:

【中文标题】Angular 5 无法从 HttpXsrfTokenExtractor 获取 XSRF 令牌【英文标题】:Angular 5 unable to get XSRF token from HttpXsrfTokenExtractor 【发布时间】:2018-06-08 17:13:45 【问题描述】:

我正在尝试通过 绝对 URL 向 Spring(基本身份验证)安全的 Rest API 发出 POST 请求。 在阅读了 Angular 省略了将 X-XSRF-TOKEN 自动插入到绝对 URL 的请求标头中之后,我尝试实现一个 HttpInterceptor 来添加令牌。

在我原来的 /signin POST 请求中,我创建了必要的 authorization: Basic 标头以确保 Spring 对请求进行身份验证。 返回的响应标头包含预期的 set-cookie 令牌:Set-Cookie:XSRF-TOKEN=4e4a087b-4184-43de-81b0-e37ef953d755; Path=/ 但是,在我的自定义拦截器类中,当我尝试从注入的 HttpXsrfTokenExtractor 获取令牌以进行下一个请求时,它返回 null

这是我的拦截器类的代码:

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() 在上述代码中返回 null。我希望它能够从我之前的 /signin 请求的 Spring (Set-Cookie) 响应标头中返回令牌。

我阅读了有关创建拦截器的相关帖子:angular4 httpclient csrf does not send x-xsrf-token

但是除了这个之外,我找不到很多关于 HttpXsrfTokenExtractor 的文档:https://angular.io/api/common/http/HttpXsrfTokenExtractor 问题:为什么 HttpXsrfTokenExtractor.getToken() 返回 null?

此外,我将拦截器类作为提供程序添加到 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 

需要注意的另一件事是,我在 Node.js 上从 localhost:3000 运行 Angular 前端,并从 localhost:8080 运行 Spring Rest 后端。 它们位于不同的端口上,因此使用 absolute urls 发出 http 请求的原因。当 Set-Cookie 来自对不同域的请求的响应时,浏览器会阻止它工作吗? 我还能错过什么吗? 感谢您的帮助。

----------------------------------------------[2018 年 1 月 7 日更新]修复 我使用 Webpack 开发服务器 来提供 Angular 代码。我通过为指向我的后端 Rest API 的 URL 配置 代理 来解决此问题。 这意味着从浏览器发出的所有请求现在只能发送到端口 3000 上的开发服务器,即使是对 Rest API 调用也是如此。 当 webpack 开发服务器 看到任何具有配置模式(例如 /api/...)的请求 url,它会替换为在 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 代码发出跨域(跨域)请求,也不再使用绝对 URL。这极大地简化了事情,因为我不再与 Angular XSRF (CSRF) 机制作斗争。它只是默认工作。我也不需要使用 HttpInterceptor 在其中手动插入 X-XSRF-TOKEN

设置开发服务器代理的额外好处是客户端请求不再是绝对的,因此我不需要更改所有用于生产的 Rest API 调用。

我希望这对遇到同样问题/理解的人有用。

Webpack 开发服务器代理文档参考:https://webpack.js.org/configuration/dev-server/#devserver-proxy

【问题讨论】:

【参考方案1】:

由于您使用不同的端口(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)。

【讨论】:

感谢您的建议并阐明了我对跨源请求 cookie 的理解。我在客户端使用 Webpack 开发服务器,所以我设法通过为我的休息呼叫配置代理来解决这个问题。我将更新我的问题以显示我做了什么。 嗨格伦我有同样的问题,但我真的不明白问题是什么。我开发了一个从 index.html 开始的简单 Angular 前端。使用 Spring(核心 + 安全性)是 4.3.0 版本,我已经描述了启用 csrf 的简单示例,但我从来没有看到来自 angular(5.2.11)的令牌,也没有来自 spring。你能帮忙吗?非常感谢 @Bomberlatinos9 除非您从 Spring 应用程序提供 Angular 应用程序或有代理,否则您可能正在发出跨源请求。我建议提出一个新问题并将您的应用程序的所有详细信息放入其中 @dsies 嗨,我已经将我的 Angular 应用程序与 spring 集成,所以所有的 rest 调用都在同一个域中。 您会在生产中使用这种方法吗?您会只使用代理来部署应用程序吗?

以上是关于Angular 5 无法从 HttpXsrfTokenExtractor 获取 XSRF 令牌的主要内容,如果未能解决你的问题,请参考以下文章

Angular 5 - 无法从一个组件导航到另一个组件

从 Visual Studio 运行 - 无法加载资源:net::ERR_CONNECTION_REFUSED - Angular、Net(5.0) app、Electron

Angular 5 - 无法分配类属性

无法以 Angular 5 下载文件

从 5 到 6 的角度迁移后无法构建 - 找不到模块“打字稿”

Angular 5 文件上传:无法在“HTMLInputElement”上设置“值”属性