Angular 5 无法从 HttpXsrfTokenExtractor 获取 XSRF 令牌 [英] Angular 5 unable to get XSRF token from HttpXsrfTokenExtractor

查看:24
本文介绍了Angular 5 无法从 HttpXsrfTokenExtractor 获取 XSRF 令牌的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I am trying to make a POST request via an absolute URL to a Spring (Basic authentication) secured Rest API.
Having read that Angular omits inserting the X-XSRF-TOKEN into the request header automatically for absolute urls, I tried to implement an HttpInterceptor to add the token in.

在我最初的 /signin POST 请求中,我创建了必要的 authorization: Basic 标头以确保 Spring 对请求进行身份验证.
返回的响应标头包含预期的 set-cookie 令牌:

Set-Cookie:XSRF-TOKEN=4e4a087b-4184-43de-81b0-e37ef953d755;路径=/

但是,在我的自定义拦截器类中,当我尝试从下一个请求注入的 HttpXsrfTokenExtractor 获取令牌时,它返回 null.

In my original /signin POST request, I create the necessary authorization: Basic header to ensure Spring authenticates the request.
The response header returned contains the expected set-cookie token:

Set-Cookie:XSRF-TOKEN=4e4a087b-4184-43de-81b0-e37ef953d755; Path=/

However, in my custom interceptor class when I try to obtain the token from the injected HttpXsrfTokenExtractor for the next request, it returns null.

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

Here is the code for my interceptor 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() 在上面的代码中返回 null.我希望它从我之前的 /signin 请求的 Spring (Set-Cookie) 响应头中返回令牌.


tokenExtractor.getToken() returns null in the above code. I expected it to return the token from Spring (Set-Cookie) response header of my previous /signin request.

我阅读了有关创建拦截器的相关帖子:
angular4 httpclient csrf 不发送 x-xsrf-token

I read this related post for creating the interceptor:
angular4 httpclient csrf does not send x-xsrf-token

但除此之外,我找不到太多有关 HttpXsrfTokenExtractor 的文档:
https://angular.io/api/common/http/HttpXsrfTokenExtractor

问题:为什么 HttpXsrfTokenExtractor.getToken() 返回 null?

But I wasn't able to find much documentation for HttpXsrfTokenExtractor other than this:
https://angular.io/api/common/http/HttpXsrfTokenExtractor

Question: Why is HttpXsrfTokenExtractor.getToken() returning null?

此外,我将拦截器类添加为 app.module 的提供者.
这是我的 app.module.ts:

In addition I added the interceptor class as a provider to the app.module.
Here is my 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 {}


另一件要注意的事情是,我在 localhost:3000 上的 Node.js 上运行我的 Angular 前端,而在 localhost:8080 上运行我的 Spring Rest 后端.它们位于不同的端口上,这也是使用绝对网址发出 http 请求的原因.当 Set-Cookie 来自对不同域上的请求的响应时,浏览器会阻止它工作吗?

我还能错过什么吗?

感谢您的帮助.


Another thing to note is that I am running my Angular front end on Node.js from localhost:3000 and my Spring Rest back-end from localhost:8080. They are on different ports, and so the reason for making http requests with absolute urls. Would the browser prevent a Set-Cookie working when it comes from a response for a request on a different domain?

Could I be missing anything else?

Thank you for any help.

----------------------------------------------
[2018 年 1 月 7 日更新]

修复

我使用 Webpack 开发服务器 来提供 Angular 代码.我通过为指向我的后端 Rest API 的 url 配置一个代理来解决这个问题.

这意味着从浏览器发出的所有请求现在只发送到端口 3000 上的开发服务器,即使是 Rest API 调用也是如此.
webpack 开发服务器 看到任何具有配置模式(例如/api/...)的请求 URL 时,它会替换为对 http://localhost:8080(就我而言).

这是我添加到 webpack.dev.js 文件的 devServer 部分的内容:

----------------------------------------------
[Updated 7th Jan 2018]

FIX

I use Webpack dev server to serve the Angular code. I worked around the issue by configuring a proxy for urls that point to my back-end Rest API.

This means all requests made from the browser now only be to the dev server at port 3000, even for the Rest API calls.
When the webpack dev server sees any request urls with the configured pattern (E.g /api/...), it replaces with calls to the back-end server on http://localhost:8080 (in my case).

This is my what I added to the devServer section of my webpack.dev.js file:

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 调用.

The added benefit of setting up a dev server proxy is that client requests are no longer absolute, so I do not need to change all the Rest API calls for production.

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

I hope this is useful for anyone who is suffering the same problem/understanding.

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

Webpack dev server proxy documentation ref:
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).

Since you are using different ports (3000 & 8080), you are making a cross-origin request, so you will not be able to read the cookie in the client sent from the server. If you want to separate your client and server in this way, you need to use a proxy so the client and server applications are served from the same protocol (http/https), domain, and port. If you are using Spring Boot I would suggest you look at Spring Cloud Netflix, specifically Zuul (https://cloud.spring.io/spring-cloud-netflix/single/spring-cloud-netflix.html#_router_and_filter_zuul).

这篇关于Angular 5 无法从 HttpXsrfTokenExtractor 获取 XSRF 令牌的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆