Angular 5 HttpInterceptor在请求之前刷新令牌

时间:2018-05-29 09:06:14

标签: javascript angular typescript rxjs angular-httpclient

我需要在发出请求之前在HttpInterceptor中刷新令牌,为此我在请求之前检查访问令牌,如果它已过期则调用刷新。 目前,我的拦截器看起来像这样:

@Injectable()
export class TokenInterceptor implements HttpInterceptor {

  private refreshTokenSubject = new BehaviorSubject(null);
  private refreshTokenObservable = this.refreshTokenSubject.asObservable();
  private isRefreshingToken = false;

  constructor(private authService: AuthService) {
  }

  intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
    const tokenData = AuthService.getCurrentSessionData();

    if (!this.isRefreshingToken) {
      // if no token set - make request as-is
      const tokenSet = tokenData && tokenData.token;
      if (!tokenSet) {
        return next.handle(request);
      }

      // proceed if token not expired
      const tokenExpired = new Date(tokenData.expirationDate) < new Date();
      if (!tokenExpired) {
        return next.handle(this.setAuthHeader(request, tokenData.token));
      }

      // check if we can refresh the token and logout instantly if not
      const tokenRefreshable = tokenData.refreshToken && new Date(tokenData.refreshTokenExpirationDate) > new Date();
      if (!tokenRefreshable) {
        this.authService.logout();
        return Observable.throw('');
      }

      this.isRefreshingToken = true;

      // make all subsequent requests wait for new token
      this.refreshTokenSubject.next(null);

      // make refresh request
      return this.authService.refreshToken()
        .switchMap((res: any) => {
          AuthService.storeSessionData(res, Utils.getLocalStorageItem(STORAGE_KEYS.STAY_LOGGED_IN));
          this.isRefreshingToken = false;

          // let subsequent awaiting proceed
          this.refreshTokenSubject.next(res.access_token);
          return next.handle(this.setAuthHeader(request, res.access_token));
        })
        .catch((err) => {
          this.authService.logout();
          return Observable.throw('');
        })
        .finally(() => {
          this.isRefreshingToken = false;
        });
    } else {
      // if token refreshing in progress - wait for new token
      return this.refreshTokenObservable
        .filter(token => token !== null)
        .take(1)
        .switchMap((token) => {
          return next.handle(this.setAuthHeader(request, token));
        });
    }
  }

  private setAuthHeader(request, token) {
    return request.clone({
      setHeaders: {
        Authorization: `Bearer ${token}`,
      },
    });
  }
}

问题是this.authService.refreshToken()永远不会发出请求,后续请求永远不会继续。我猜它是因为没有订阅从HttpClient返回的observable,这里是refreshToken方法代码:

public refreshToken() {
    const tokenData = AuthService.getCurrentSessionData();

    return this.http.post(
      `${environment.apiPath}/auth/refresh`,
      { refresh_token: tokenData.refreshToken },
    );
}

如何修复此代码以发出refreshToken请求,并让其他请求按预期继续进行?

1 个答案:

答案 0 :(得分:1)

 @Injectable()
    export class RequestInterceptorService implements HttpInterceptor {

      @BlockUI() blockUI: NgBlockUI;
      isRefreshingToken = false;
      tokenSubject: BehaviorSubject<string> = new BehaviorSubject<string>(null);
      oldToken = localStorage.getItem('access_token');
      constructor(private authService: AuthService, private localStorageToken: AppLocalStorage,
                  private route: ActivatedRoute, private router: Router) {}

      addToken(req: HttpRequest<any>, token: string): HttpRequest<any> {
        return req.clone({ setHeaders: { Authorization: 'Bearer ' + token, 'Access-Control-Allow-Origin': '*' }});
      }

      intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpSentEvent | HttpHeaderResponse
        | HttpProgressEvent | HttpResponse<any> | HttpUserEvent<any>> {
        return next.handle(this.addToken(req, this.authService.getAuthToken()))
          .catch(error => {
            if (error instanceof HttpErrorResponse) {
              switch ((<HttpErrorResponse>error).status) {
                case 400:
                  return this.handle400Error(error);
                case 401: this.authService.refresh().subscribe((data) => { // A call has been made at an instant where network get 401 status code,It will prevent the asynchronous way of handling network calls.
                    this.localStorageToken.setRefreshTokens(data); //localstorageToken is used to store all the response, including access token, expiry time and refresh token to get stored in localstorage.
                  }
                );
                  return this.handle401Error(req, next);
                // default: this.logoutUser();
              }
            } else {
              return Observable.throw(error);
            }
          });

      }

      handle400Error(error) {
        if (error && error.status === 400 && error.error && error.error.error === 'invalid_grant') {
          // If we get a 400 and the error message is 'invalid_grant', the token is no longer valid so logout.
          // console.log('Bad Error');
          return this.logoutUser();
        }

        return Observable.throw(error);
      }

      handle401Error(req: HttpRequest<any>, next: HttpHandler) {
        if (!this.isRefreshingToken) {
          this.isRefreshingToken = true;

          // Reset here so that the following requests wait until the token
          // comes back from the refreshToken call.
          this.tokenSubject.next(null);

          return this.authService.refreshToken()
            .switchMap((newToken: string) => {
              newToken = localStorage.getItem('access_token');
              if (newToken) {
                this.tokenSubject.next(newToken);
                if (this.oldToken === newToken) {
                  return this.logoutUser();
                } else {
                  return next.handle(this.addToken(req, newToken));
                }
              }

              // If we don't get a new token, we are in trouble so logout.
              return this.logoutUser();
            })
            .catch(error => {
              // If there is an exception calling 'refreshToken', bad news so logout.
              return this.logoutUser();
            })
            .finally(() => {
              this.isRefreshingToken = false;
            });
        } else {
          return this.tokenSubject
            .filter(token => token != null)
            .take(1)
            .switchMap(token => {
              return next.handle(this.addToken(req, token));
            });
        }
      }

      logoutUser() {
        // Route to the login page (implementation up to you)
        this.router.navigate(['login']).then(() => { this.blockUI.stop(); });
        return Observable.throw('');
      }
    }
  

现在AuthService用于获取刷新令牌和登录,   每当需要刷新令牌时都会调用此服务,我花了2秒钟的时间来获取刷新令牌

    export class AuthService {
      private TokenApi = AppSettings.DEVELOPMENT_API;
      private newToken = ' ';
      private current_token: string;
      private refresh_token: string = localStorage.getItem('refresh_token');

      constructor(private http: HttpClient, private localStorageToken: AppLocalStorage) {
      }

      login(username: string, password: string): Observable<TokenParams> {
        const headers = new HttpHeaders({'content-type': 'application/x-www-form-urlencoded'});
        const loginApi = this.TokenApi + '/oauth/token?username=' + username + '&password=' + password + '&grant_' +
          'type=password....';
        return this.http.post<TokenParams>(loginApi, '', {headers: headers});
      }

      refresh(): Observable<TokenParams> {
        this.refresh_token = localStorage.getItem('refresh_token');
        const refreshToken = this.TokenApi + '/oauth/token?refresh_token=' + this.refresh_token + '&grant_' +
          'type=refresh_token...';
        return this.http.post<TokenParams>(refreshToken, '' );
      }

      logout() {
        this.localStorageToken.emptyLocalStorage();
      }

      getAuthToken() {
        this.current_token = localStorage.getItem('access_token');
        return this.current_token;
      }

      refreshToken(): Observable<string> {
       this.newToken = localStorage.getItem('access_token');
        this.current_token = this.newToken;
        return Observable.of(localStorage.getItem('access_token')).delay(2000);
      }

    }