【问题标题】:angular4 httpclient csrf does not send x-xsrf-tokenangular4 httpclient csrf 不发送 x-xsrf-token
【发布时间】:2025-12-26 18:05:16
【问题描述】:

在 Angular 文档中,提到了 Angular httpclient 会在 post 请求的标头 X-XSRF-TOKEN 中自动发送 cookie XSRF-TOKEN 的值。 Documentation link

但它不会为我发送标题。这是我的代码

设置 cookie 的 Nodejs 代码

router.get('/set-csrf',function(req,res,next){
    res.setHeader('Set-Cookie', "XSRF-TOKEN=abc;Path=/; HttpOnly; SameSite=Strict");    
    res.send();
  })

我在 app.module.ts 中使用过 httpclient

imports: [
  HttpClientModule
]

** 以上代码仅用于调试目的。我没有 set-csrf 端点。

但是当我发送 post 请求时它不会发送任何标题。我无法调试。

我也在 Angular 的 github 存储库中添加了这个问题。 HttpXsrfInterceptor 检查请求是 GET 还是 HEAD,或者它是否以 http 开头。如果为 true,则跳过添加标题。

这是HttpXsrfInterceptor class中的代码

intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
    const lcUrl = req.url.toLowerCase();
    // Skip both non-mutating requests and absolute URLs.
    // Non-mutating requests don't require a token, and absolute URLs require special handling
    // anyway as the cookie set
    // on our origin is not the same as the token expected by another origin.
    if (req.method === 'GET' || req.method === 'HEAD' || lcUrl.startsWith('http://') ||
        lcUrl.startsWith('https://')) {
      return next.handle(req);
    }
    const token = this.tokenService.getToken();

    // Be careful not to overwrite an existing header of the same name.
    if (token !== null && !req.headers.has(this.headerName)) {
      req = req.clone({headers: req.headers.set(this.headerName, token)});
    }
    return next.handle(req);
  }

我不确定他们为什么跳过 http/s 部分。这是我的issue in github

【问题讨论】:

  • 你使用 CORS 请求吗?
  • 我正在添加标题"Access-Control-Allow-Headers","*"
  • 我看到了你的问题。在我看来,角度应该以不同的方式处理 http[s] 链接。例如,分别存储每个域的最后一个 csrf 令牌。让另一个拦截器绞盘以这种方式处理 csrf 是否很好?
  • 我不明白的一件事(我在github问题中也提到过)为什么http url需要特殊处理?因为 owasp 指南和*文章都没有提到任何此类情况。你提到为每个域存储 csrf 令牌,你能解释一下这个 http/https 和多个域吗?我猜这仅适用于多域场景(子域),但它与以 http/https 开头的 url 有什么关系?
  • 我的意思是,如果 url 以 http/https Angular 应用程序调用后端在不同的资源上,加载的 Angular 应用程序不同。它可以是子域场景,甚至可以是允许 CORS 访问的完全不同的站点。因此,如果它是不同的域,他们每个人都不能互相认识,并且他们分别发送 csrf 令牌。因此,如果 Angular 应用程序看到 url 以 http 开头,则不应发送从不以 http 开头的 url 获得的 csrf 令牌,因为它可能导致泄露令牌。而不是这个应用程序应该跟踪由域分割的 csrf 令牌,并将令牌发送到从中获取它的域。

标签: angular cookies csrf-protection x-xsrf-token


【解决方案1】:

您正在寻找的是HttpClientXsrfModule

请在此处阅读更多信息:https://angular.io/api/common/http/HttpClientXsrfModule

你的用法应该是这样的:

imports: [   
 HttpClientModule,  
 HttpClientXsrfModule.withOptions({
   cookieName: 'My-Xsrf-Cookie', // this is optional
   headerName: 'My-Xsrf-Header' // this is optional
 }) 
]

此外,如果您的代码通过绝对 URL 定位 API,默认的 CSRF 拦截器将无法开箱即用。相反,您必须实现自己的拦截器,它不会忽略绝对路由。

@Injectable()
export class HttpXsrfInterceptor implements HttpInterceptor {

  constructor(private tokenExtractor: HttpXsrfTokenExtractor) {
  }

  intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
    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);
  }
}

最后将其添加到您的提供商:

providers: [
  { provide: HTTP_INTERCEPTORS, useClass: HttpXsrfInterceptor, multi: true }
]

【讨论】:

  • 它会将 CSRF 令牌添加到所有请求还是仅添加数据更改请求(POST、PUT、DELETE)?
  • 您使用 GET 获取 CSRF 令牌,因此即使您没有通过它,GET 也会为您返回它(当然关于用户身份验证),并且从那里每次请求都会携带它 - 由于 POST、PUT、DELETE、PATCH 正在更改数据,因此拥有 CSRF 至关重要。
  • 我的意思是,使用您提供的代码,所有请求都将设置令牌,甚至是 GET 请求。我不认为这是故意的。
  • @Miroslav - 我不明白你对绝对和相对路径的评论。在 Web 服务中,路径不总是绝对的吗?否则,客户端如何知道服务器在哪里(或者客户端是否从初始 GET 的响应中存储服务器的信息)?
  • 您的解决方案对我有用。非常感谢。我将url 从绝对更改为相对。虽然我不明白为什么绝对和相对之间的行为不同。你能解释一下吗?
【解决方案2】:

我想正确的方法是withOptions。我使用 withConfig 并收到错误 Property 'withConfig' does not exist on type 'typeof HttpClientXsrfModule'. 这是文档中的打字问题。您需要使用“withOptions”而不是HttpClientXsrfModule.withOptions({ cookieName: 'My-Xsrf-Cookie', headerName: 'My-Xsrf-Header', })

【讨论】:

    【解决方案3】:

    使用最近的 Angular 版本,我遇到了以下问题。当令牌使用标头名称“XSRF-TOKEN”传递给客户端时,响应必须使用标头名称“X-XSRF-TOKEN”反馈令牌。所以这里是 Miroslav 上面代码的一个稍微修改过的版本,它适用于我。

    @Injectable()
    export class HttpXSRFInterceptor implements HttpInterceptor {
    
      constructor(private tokenExtractor: HttpXsrfTokenExtractor) {
      }
    
      intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
        const headerName = 'XSRF-TOKEN';
        const respHeaderName = 'X-XSRF-TOKEN';
        let token = this.tokenExtractor.getToken() as string;
        if (token !== null && !req.headers.has(headerName)) {
          req = req.clone({ headers: req.headers.set(respHeaderName, token) });
        }
        return next.handle(req);
      }
    }
    

    【讨论】: