Angular 7 - How can I share the token renewal Obse

2019-07-25 02:28发布

Before each request a HTTP interceptor checks if the access token is expired and if so, it first renews the token and then continues the request.

The problem is on pages that have multiple requests. On those pages the interceptor tries to renew the token for each request.

How can I share the token renewal request in this case?

The method that is responsible for token renewal is this:

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
      );
}

And this is how the interceptor uses that method:

...
// 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);
    })
  );
...

1条回答
我想做一个坏孩纸
2楼-- · 2019-07-25 03:01

You need to check if refresh token request is in progress or not because you don’t want other calls to come in and call refreshToken again.

Here I've created RefreshTokenInterceptor class for you.

You just need to customize it and follow the comments:

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
            }
        });
    }

}
查看更多
登录 后发表回答