0

Angular 有时无法添加 XSRF 令牌,因此我们添加了拦截器做双重检查并在丢失时添加令牌。然后我们发现它有时无法读取 cookie,所以我们添加了 3rd 方库来读取 cookie.. 但是我们仍然面临第一次尝试时缺少 XSRF 令牌的 prod 错误,特别是当从另一个站点重定向到我们这边时,随后刷新页面工作正常。

我们的一个理论是 cookie 尚未设置,即在实际设置 cookie 之前运行 cookie 的角度读取的竞争条件。认为遇到此错误的流量很低,我们需要解决这些错误并减少客户的挫败感。

现在我们想将请求保持几毫秒然后读取 cookie,即使没有找到 cookie,我们也想执行第二次 /ws/bootstrap 调用。

我很难理解或想出这个拦截器中的代码。非常感谢任何建议或参考。

同时,我将尝试在评论中发布我修改后的代码,但到目前为止,这几乎是无效的代码。

import { HttpEvent, HttpHandler, HttpInterceptor, HttpRequest, HttpXsrfTokenExtractor } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { ILoggerInstance, LoggerService } from '@foo/logger-angular';
import { CookieService } from 'ngx-cookie-service';
import { Observable } from 'rxjs';

// Angular adds XSRF header only for Post, below code adds for all GET (i.e. any halHttp call)
// https://github.com/angular/angular/issues/20511
@Injectable()
export class HttpXsrfInterceptor implements HttpInterceptor {
  private readonly log: ILoggerInstance;
  private readonly xsrfHeaderName = 'X-XSRF-TOKEN';
  private readonly xsrfCookieName = 'XSRF-TOKEN';

  constructor(
    private readonly tokenExtractor: HttpXsrfTokenExtractor,
    private readonly cookieService: CookieService,
    private readonly logger: LoggerService
  ) {
    this.log = this.logger.getInstance('HttpXsrfInterceptor');
  }

  intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
    if (!req.headers.has(this.xsrfHeaderName)) {
      // We have seen header missing in prod for approx 10% traffic, so adding non angular way of retrieving cookie if angular failed for some reason.
      const token = this.tokenExtractor.getToken() || this.cookieService.get(this.xsrfCookieName);
      if (token) {
        req = req.clone({ headers: req.headers.set(this.xsrfHeaderName, token) });
      } else if (req.url !== 'ws/bootstrap' && req.url !== 'bootstrap') {
        // Exclude bootstrap it issues xsrf cookie
        this.log.error('Missing xsrf cookie');
      }
    }
    return next.handle(req);
  }
}

4

2 回答 2

0

我可以向您建议一个简单的技巧,以对价值进行廉价的“轮询”:

intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
   const req$ = req.url === 'ws/bootstrap' || req.url === 'bootstrap'
      ? of(req)
      : this.addXsrfToken(req);

    return req$.pipe(switchMap(newReq => next.handle(newReq)));
  }

addXsrfToken(request: HttpRequest<any>) {
   return timer(0, 100).pipe( // polling every 100msec
     map(() => this.tokenExtractor.getToken()), // try to read token
     first(token => !!token), // first token that is not null
     map(token => req.clone({ headers: req.headers.set(this.xsrfHeaderName, token) })) // will be converted to event
   );
}

如果您打算做出很好的解决方案,这是非常可扩展的。你真的应该替换timer().(first)你的逻辑。其余的将保持不变。理想情况下,您希望异步处理您的令牌值。在这种情况下,值将是发送请求的事件

于 2021-01-11T23:03:14.137 回答
0

理想情况下,不必要的角度应该是开箱即用的,但它不适用于任何外部浏览器/网络组合。

这是我们的最终代码,错误已从几千降至个位数。

    import { HttpEvent, HttpHandler, HttpInterceptor, HttpRequest, HttpXsrfTokenExtractor } from '@angular/common/http';
    import { Injectable } from '@angular/core';
    import { ILoggerInstance, LoggerService } from '@xyz/logger-angular';
    import { Observable } from 'rxjs';
    import { BootstrapService } from '../services/bootstrap.service';
    
    @Injectable()
    export class HttpXsrfInterceptor implements HttpInterceptor {
      private readonly log: ILoggerInstance;
      private readonly xsrfHeaderName = 'X-XSRF-TOKEN';
    
      constructor(
        private readonly tokenExtractor: HttpXsrfTokenExtractor,
        private readonly logger: LoggerService,
        private readonly bootstrapService: BootstrapService
      ) {
        this.log = this.logger.getInstance('HttpXsrfInterceptor');
      }
    
      intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
        if (!req.headers.has(this.xsrfHeaderName)) {
          // We have seen header missing in prod for approx 10% traffic, so adding non angular way of retrieving cookie if angular failed for some reason.
          const token = this.tokenExtractor.getToken() || this.bootstrapService.getXsrfToken();
          if (token) {
            req = req.clone({ headers: req.headers.set(this.xsrfHeaderName, token) });
          } else if (req.method === 'POST' && !req.url.endsWith('bootstrap')) {
            // Exclude GET and /ws/bootstrap as it issues xsrf cookie
            this.log.error('Missing xsrf cookie');
          }
        }
        return next.handle(req);
      }
    }

于 2021-10-02T05:12:08.420 回答