Angular 7 - 如何共享令牌更新 Observable?

Angular 7 - How can I share the token renewal Observable?

在每个请求之前,HTTP 拦截器会检查访问令牌是否过期,如果是,它会首先更新令牌,然后继续请求。

问题出在有多个请求的页面上。在这些页面上,拦截器尝试为每个请求更新令牌。

在这种情况下,如何共享令牌续订请求?

负责更新令牌的方法是这样的:

renewToken() {

    let refreshToken = // get refresh token

    let accessToken = // get access token

    if (!refreshToken || !accessToken)
      return;

    let requestData = {
      accessToken: accessToken,
      refreshToken: refreshToken
    }

    return this.http.post<ApplicationToken>(`${this.baseUrl}/renewToken`, requestData)
      .pipe(
        // I tried share() shareReplay(1) and publishReplay(1) here but no luck
      );
}

这就是拦截器使用该方法的方式:

...
// in case we need to renew the token
return this.accountService.renewToken()
  .pipe(                        
    switchMap(t => {

      this.accountService.saveToken(t);
      token = this.accountService.getAccessToken();

      var newReq = this.setToken(req, token);
      return next.handle(newReq);
    })
  );
...

您需要检查刷新令牌请求是否正在进行,因为您不希望其他调用进来并再次调用 refreshToken。

我在这里为您创建了 RefreshTokenInterceptor class。

你只需要定制,关注评论即可:

import { Injectable } from "@angular/core";
import { HttpInterceptor, HttpEvent, HttpRequest, HttpHandler } from "@angular/common/http";
import { BehaviorSubject, Observable } from "rxjs";
import { catchError, switchMap, filter, take } from "rxjs/operators";

@Injectable({
    providedIn: 'root'
})
export class RefreshTokenInterceptor implements HttpInterceptor {

    private refreshTokenInProgress: boolean = false;

    private refreshTokenSubject: BehaviorSubject<any> = new BehaviorSubject<any>(
        null
    );

    constructor(public accountService: AccountService) {}

    intercept(request: HttpRequest<any>,next: HttpHandler): Observable<HttpEvent<any>> {

        // Check first if token has expired
        // If not, just add addAuthenticationToken();

        // If expired
        if (tokenHasExpired()) {

            if (this.refreshTokenInProgress) {

                // If refreshTokenInProgress is true, we will wait until refreshTokenSubject has a non-null value
                // – which means the new token is ready and we can retry the request again
                return this.refreshTokenSubject.pipe(
                    filter(result => result !== null),
                    take(1),
                    switchMap(() => next.handle(this.addAuthenticationToken(request)))
                );

            } else {

                this.refreshTokenInProgress = true;

                // Set the refreshTokenSubject to null so that subsequent API calls will wait until the new token has been retrieved
                this.refreshTokenSubject.next(null);

                return this.accountService.renewToken()
                        .pipe(                        
                            switchMap(t => {

                                this.accountService.saveToken(t);
                                let token = this.accountService.getAccessToken();

                                this.refreshTokenInProgress = false; // Set refreshTokenInProgress to False
                                this.refreshTokenSubject.next(token); // Add token to the refreshTokenSubject

                                var newReq = this.setToken(req, token);
                                return next.handle(newReq);

                            }),
                            catchError((err) => {

                                this.refreshTokenInProgress = false;
                                return Observable.throw(err);

                            })
                        );
            }

        } else {

            return this.addAuthenticationToken(request);

        }

    }

    addAuthenticationToken(request) {
        // Get access token from Local Storage
        const accessToken = this.accountService.getAccessToken();

        // If access token is null this means that user is not logged in
        // And we return the original request
        if (!accessToken) {
            return request;
        }

        // We clone the request, because the original request is immutable
        return request.clone({
            setHeaders: {
                Authorization: accessToken
            }
        });
    }

}