防止在 Angular 中多次触发令牌刷新请求

Posted

技术标签:

【中文标题】防止在 Angular 中多次触发令牌刷新请求【英文标题】:Prevent Token Refresh Request From Being Fired Multiple Times in Angular 【发布时间】:2021-01-24 03:34:13 【问题描述】:

我正在使用 JWT 构建一个基本的身份验证系统。以下是基本工作流程:

在向 API 发送请求之前,我有一个 HttpInterceptor 来检查令牌是否已过期。如果它还没有过期,拦截器将使用 JWT 将授权标头附加到请求中。如果它已过期,则需要在发送实际请求之前刷新令牌。当刷新请求到达后端时,它会根据数据库检查刷新令牌并删除条目,使其只能使用一次,然后返回一个新的 JWT + 刷新令牌。

我的一些页面在访问它们时会触发多个请求,问题就来了。将一次发送多个引用请求,因此只有第一个到达后端的请求会成功返回。所有其他请求将返回 401 错误,这会导致客户端出现问题。

因此,我正在寻找一种方法来暂停所有请求,直到第一个刷新请求返回。 HttpInterceptor 调用一个函数,该函数通过检查过期日期并在过期时触发刷新请求来返回 JWT。

令牌拦截器:

intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> 
    // Get access token
    this._auth.getAccessToken().subscribe((accessToken: string) => 
        request = request.clone(
            setHeaders: 
                Authorization: `Bearer $accessToken`
            
        );
        return next.handle(request);
    );

获取访问令牌功能:

public getAccessToken(): Observable<string> 
    if (!this._authUser) return throwError("User is not logged in.");   // Make sure user is logged in

    // Refresh token
    if (this.checkTokenExpired())
        return this.refreshToken().pipe(map(() => this._authUser.tokenManager.accessToken));

    return of(this._authUser.tokenManager.accessToken);

对于同步请求,此架构成为一个问题,因为刷新请求将被发送多次。我需要一个类似互斥锁的东西来停止除第一个请求之外的所有请求,并在刷新令牌后释放所有请求并返回新的 JWT。

【问题讨论】:

你试过 share() 方法吗? return this.refreshToken().pipe(share(), map(() => this._authUser.tokenManager.accessToken)); 刚试过,不行@Ilya 【参考方案1】:

感谢下面的帖子,我能够得到我想要的实现:https://***.com/a/54328099/5203853

这就是我实现refreshToken() 函数的样子:

// Variables
private _tokenSubject: BehaviorSubject<auth.User> = new BehaviorSubject<auth.User>(null);
private _isRefreshingToken: Boolean = false;

private refreshToken(): Observable<auth.User> 
    if (!this._authUser) return throwError("User is not logged in."); // Make sure user is logged in

    if (!this._isRefreshingToken) 
        this._isRefreshingToken = true;
        // Reset such that the following requests wait until the token
        // comes back from the refreshToken call.
        this._tokenSubject.next(null);

        let req = this._httpClient.post<AuthService.authUser>('/api/auth/refresh',  
            accessToken: this._authUser.tokenManager.accessToken,
            refreshToken: this._authUser.tokenManager.refreshToken 
        )

        return req.pipe(
            tap(authUser => 
                this.saveUser(authUser);
                // Emit event & return promise
                this.onAuthStateChange.emit(authUser.user);
                // Retry previous request
                this._tokenSubject.next(authUser.user);
            ),
            map(authUser => authUser.user),
            catchError((err: HttpErrorResponse) => 
                this.signOut();
                this._router.navigate(['/auth/login']);
                return throwError(err.message);
            ), 
            finalize(() => 
                this._isRefreshingToken = false;
            )
        );
    
    else 
        return this._tokenSubject
            .pipe(
                filter(authUser => authUser != null), 
                take(1)
            );
    

【讨论】:

以上是关于防止在 Angular 中多次触发令牌刷新请求的主要内容,如果未能解决你的问题,请参考以下文章

Angular 7 自动刷新不记名令牌

Angular 10 应用程序不会在跨域请求中发送 JWT 刷新令牌

在 Angular 5 中,静默刷新不适用于 OIDC 客户端

Angular 4 和 OAuth - 拦截 401 响应,刷新访问令牌并重试请求

使用带有 JWT Auth/Laravel 的 Angular JS 获取新的刷新令牌后,如何恢复/重新发送请求?

Angular 4.3 - HTTP 拦截器 - 刷新 JWT 令牌