问题描述
Angular 有时无法添加 XSRF 令牌,因此我们添加了拦截器进行双重检查并在丢失时添加令牌。然后我们发现它有时无法读取 cookie,所以我们添加了 3rd 方库来读取 cookie.. 但我们仍然面临生产错误,其中 XSRF 令牌在第一次尝试时丢失,尤其是当从另一个站点重定向到我们这边时,页面的后续刷新工作正常。
我们的一个理论是 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);
}
}
解决方法
我可以向您推荐一个简单的方法来对价值进行廉价的“轮询”:
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)
替换为您的逻辑。其余的将保持不变。理想情况下,您希望异步处理您的令牌值。在这种情况下,值将是发送请求的事件