簡體   English   中英

Angular 4 令牌刷新后攔截器重試請求

[英]Angular 4 Interceptor retry requests after token refresh

嗨,我想弄清楚如何通過刷新令牌並重試請求來實現新的 angular 攔截器並處理401 unauthorized的錯誤。 這是我一直在關注的指南: https://ryanchenkie.com/angular-authentication-using-the-http-client-and-http-interceptors

我成功緩存了失敗的請求並且可以刷新令牌,但我不知道如何重新發送以前失敗的請求。 我也想讓它與我目前使用的解析器一起工作。

token.interceptor.ts

return next.handle( request ).do(( event: HttpEvent<any> ) => {
        if ( event instanceof HttpResponse ) {
            // do stuff with response if you want
        }
    }, ( err: any ) => {
        if ( err instanceof HttpErrorResponse ) {
            if ( err.status === 401 ) {
                console.log( err );
                this.auth.collectFailedRequest( request );
                this.auth.refreshToken().subscribe( resp => {
                    if ( !resp ) {
                        console.log( "Invalid" );
                    } else {
                        this.auth.retryFailedRequests();
                    }
                } );

            }
        }
    } );

身份驗證.service.ts

cachedRequests: Array<HttpRequest<any>> = [];

public collectFailedRequest ( request ): void {
    this.cachedRequests.push( request );
}

public retryFailedRequests (): void {
    // retry the requests. this method can
    // be called after the token is refreshed
    this.cachedRequests.forEach( request => {
        request = request.clone( {
            setHeaders: {
                Accept: 'application/json',
                'Content-Type': 'application/json',
                Authorization: `Bearer ${ this.getToken() }`
            }
        } );
        //??What to do here
    } );
}

上面的 retryFailedRequests() 文件是我想不通的。 重試后如何重新發送請求並通過解析器使它們可用於路由?

如果有幫助,這是所有相關代碼: https://gist.github.com/joshharms/00d8159900897dc5bed45757e30405f9

我的最終解決方案。 適用於並行請求。

更新:使用 Angular 9 / RxJS 6 更新的代碼,錯誤處理和修復 refreshToken 失敗時的循環

import { HttpRequest, HttpHandler, HttpInterceptor, HTTP_INTERCEPTORS } from "@angular/common/http";
import { Injector } from "@angular/core";
import { Router } from "@angular/router";
import { Subject, Observable, throwError } from "rxjs";
import { catchError, switchMap, tap} from "rxjs/operators";
import { AuthService } from "./auth.service";

export class AuthInterceptor implements HttpInterceptor {

    authService;
    refreshTokenInProgress = false;

    tokenRefreshedSource = new Subject();
    tokenRefreshed$ = this.tokenRefreshedSource.asObservable();

    constructor(private injector: Injector, private router: Router) {}

    addAuthHeader(request) {
        const authHeader = this.authService.getAuthorizationHeader();
        if (authHeader) {
            return request.clone({
                setHeaders: {
                    "Authorization": authHeader
                }
            });
        }
        return request;
    }

    refreshToken(): Observable<any> {
        if (this.refreshTokenInProgress) {
            return new Observable(observer => {
                this.tokenRefreshed$.subscribe(() => {
                    observer.next();
                    observer.complete();
                });
            });
        } else {
            this.refreshTokenInProgress = true;

            return this.authService.refreshToken().pipe(
                tap(() => {
                    this.refreshTokenInProgress = false;
                    this.tokenRefreshedSource.next();
                }),
                catchError(() => {
                    this.refreshTokenInProgress = false;
                    this.logout();
                }));
        }
    }

    logout() {
        this.authService.logout();
        this.router.navigate(["login"]);
    }

    handleResponseError(error, request?, next?) {
        // Business error
        if (error.status === 400) {
            // Show message
        }

        // Invalid token error
        else if (error.status === 401) {
            return this.refreshToken().pipe(
                switchMap(() => {
                    request = this.addAuthHeader(request);
                    return next.handle(request);
                }),
                catchError(e => {
                    if (e.status !== 401) {
                        return this.handleResponseError(e);
                    } else {
                        this.logout();
                    }
                }));
        }

        // Access denied error
        else if (error.status === 403) {
            // Show message
            // Logout
            this.logout();
        }

        // Server error
        else if (error.status === 500) {
            // Show message
        }

        // Maintenance error
        else if (error.status === 503) {
            // Show message
            // Redirect to the maintenance page
        }

        return throwError(error);
    }

    intercept(request: HttpRequest<any>, next: HttpHandler): Observable<any> {
        this.authService = this.injector.get(AuthService);

        // Handle request
        request = this.addAuthHeader(request);

        // Handle response
        return next.handle(request).pipe(catchError(error => {
            return this.handleResponseError(error, request, next);
        }));
    }
}

export const AuthInterceptorProvider = {
    provide: HTTP_INTERCEPTORS,
    useClass: AuthInterceptor,
    multi: true
};

使用最新版本的 Angular (7.0.0) 和 rxjs (6.3.3),這就是我創建一個功能齊全的自動會話恢復攔截器的方式,確保如果並發請求因 401 而失敗,那么它也應該只命中令牌刷新 API一次並將失敗的請求通過管道傳遞給使用 switchMap 和 Subject 的響應。 下面是我的攔截器代碼的樣子。 我省略了我的身份驗證服務和商店服務的代碼,因為它們是非常標准的服務類。

import {
  HttpErrorResponse,
  HttpEvent,
  HttpHandler,
  HttpInterceptor,
  HttpRequest
} from "@angular/common/http";
import { Injectable } from "@angular/core";
import { Observable, Subject, throwError } from "rxjs";
import { catchError, switchMap } from "rxjs/operators";

import { AuthService } from "../auth/auth.service";
import { STATUS_CODE } from "../error-code";
import { UserSessionStoreService as StoreService } from "../store/user-session-store.service";

@Injectable()
export class SessionRecoveryInterceptor implements HttpInterceptor {
  constructor(
    private readonly store: StoreService,
    private readonly sessionService: AuthService
  ) {}

  private _refreshSubject: Subject<any> = new Subject<any>();

  private _ifTokenExpired() {
    this._refreshSubject.subscribe({
      complete: () => {
        this._refreshSubject = new Subject<any>();
      }
    });
    if (this._refreshSubject.observers.length === 1) {
      this.sessionService.refreshToken().subscribe(this._refreshSubject);
    }
    return this._refreshSubject;
  }

  private _checkTokenExpiryErr(error: HttpErrorResponse): boolean {
    return (
      error.status &&
      error.status === STATUS_CODE.UNAUTHORIZED &&
      error.error.message === "TokenExpired"
    );
  }

  intercept(
    req: HttpRequest<any>,
    next: HttpHandler
  ): Observable<HttpEvent<any>> {
    if (req.url.endsWith("/logout") || req.url.endsWith("/token-refresh")) {
      return next.handle(req);
    } else {
      return next.handle(req).pipe(
        catchError((error, caught) => {
          if (error instanceof HttpErrorResponse) {
            if (this._checkTokenExpiryErr(error)) {
              return this._ifTokenExpired().pipe(
                switchMap(() => {
                  return next.handle(this.updateHeader(req));
                })
              );
            } else {
              return throwError(error);
            }
          }
          return caught;
        })
      );
    }
  }

  updateHeader(req) {
    const authToken = this.store.getAccessToken();
    req = req.clone({
      headers: req.headers.set("Authorization", `Bearer ${authToken}`)
    });
    return req;
  }
}

根據@anton-toshik 的評論,我認為在一篇文章中解釋這段代碼的功能是個好主意。 您可以在這里閱讀我的文章以解釋和理解這段代碼(它是如何工作的以及為什么工作?)。 希望它有幫助。

我也遇到了類似的問題,我認為收集/重試邏輯過於復雜。 相反,我們可以只使用 catch 操作符來檢查 401,然后觀察令牌刷新,並重新運行請求:

return next.handle(this.applyCredentials(req))
  .catch((error, caught) => {
    if (!this.isAuthError(error)) {
      throw error;
    }
    return this.auth.refreshToken().first().flatMap((resp) => {
      if (!resp) {
        throw error;
      }
      return next.handle(this.applyCredentials(req));
    });
  }) as any;

...

private isAuthError(error: any): boolean {
  return error instanceof HttpErrorResponse && error.status === 401;
}

Andrei Ostrovski 的最終解決方案非常有效,但如果刷新令牌也已過期(假設您正在調用 api 來刷新),則它不起作用。 經過一番挖掘,我意識到刷新令牌API調用也被攔截器攔截了。 我不得不添加一個 if 語句來處理這個問題。

 intercept( request: HttpRequest<any>, next: HttpHandler ):Observable<any> {
   this.authService = this.injector.get( AuthenticationService );
   request = this.addAuthHeader(request);

   return next.handle( request ).catch( error => {
     if ( error.status === 401 ) {

     // The refreshToken api failure is also caught so we need to handle it here
       if (error.url === environment.api_url + '/refresh') {
         this.refreshTokenHasFailed = true;
         this.authService.logout();
         return Observable.throw( error );
       }

       return this.refreshAccessToken()
         .switchMap( () => {
           request = this.addAuthHeader( request );
           return next.handle( request );
         })
         .catch((err) => {
           this.refreshTokenHasFailed = true;
           this.authService.logout();
           return Observable.throw( err );
         });
     }

     return Observable.throw( error );
   });
 }

我必須解決以下要求:

  • ✅ 多個請求只刷新一次令牌
  • ✅ 如果 refreshToken 失敗,則注銷用戶
  • ✅ 如果用戶在第一次刷新后出現錯誤,則注銷
  • ✅ 在刷新令牌時將所有請求排隊

因此,為了在 Angular 中刷新令牌,我收集了不同的選項:

  • tokenRefreshed$ BehaviorSubject 作為信號量的蠻力解決方案
  • catchError RxJS 運算符中使用caught參數重試請求失敗的請求
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
    let retries = 0;
    return this.authService.token$.pipe(
      map(token => req.clone({ setHeaders: { Authorization: `Bearer ${token}` } })),
      concatMap(authReq => next.handle(authReq)),
      // Catch the 401 and handle it by refreshing the token and restarting the chain
      // (where a new subscription to this.auth.token will get the latest token).
      catchError((err, restart) => {
        // If the request is unauthorized, try refreshing the token before restarting.
        if (err.status === 401 && retries === 0) {
          retries++;
    
          return concat(this.authService.refreshToken$, restart);
        }
    
        if (retries > 0) {
          this.authService.logout();
        }
    
        return throwError(err);
      })
    );
}
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
    return this.authService.token$.pipe(
      map(token => req.clone({ setHeaders: { Authorization: `Bearer ${token}` } })),
      concatMap(authReq => next.handle(authReq)),
      retryWhen((errors: Observable<any>) => errors.pipe(
        mergeMap((error, index) => {
          // any other error than 401 with {error: 'invalid_grant'} should be ignored by this retryWhen
          if (error.status !== 401) {
            return throwError(error);
          }
    
          if (index === 0) {
            // first time execute refresh token logic...
            return this.authService.refreshToken$;
          }
    
          this.authService.logout();
          return throwError(error);
        }),
        take(2)
        // first request should refresh token and retry,
        // if there's still an error the second time is the last time and should navigate to login
      )),
    );
}

所有這些選項都經過了全面測試,可以在angular-refresh-token github repo 中找到

另見:

基於這個例子,這是我的作品

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

    constructor(private loginService: LoginService) { }

    /**
     * Intercept request to authorize request with oauth service.
     * @param req original request
     * @param next next
     */
    intercept(req: HttpRequest<any>, next: HttpHandler): Observable<any> {
        const self = this;

        if (self.checkUrl(req)) {
            // Authorization handler observable
            const authHandle = defer(() => {
                // Add authorization to request
                const authorizedReq = req.clone({
                    headers: req.headers.set('Authorization', self.loginService.getAccessToken()
                });
                // Execute
                return next.handle(authorizedReq);
            });

            return authHandle.pipe(
                catchError((requestError, retryRequest) => {
                    if (requestError instanceof HttpErrorResponse && requestError.status === 401) {
                        if (self.loginService.isRememberMe()) {
                            // Authrozation failed, retry if user have `refresh_token` (remember me).
                            return from(self.loginService.refreshToken()).pipe(
                                catchError((refreshTokenError) => {
                                    // Refresh token failed, logout
                                    self.loginService.invalidateSession();
                                    // Emit UserSessionExpiredError
                                    return throwError(new UserSessionExpiredError('refresh_token failed'));
                                }),
                                mergeMap(() => retryRequest)
                            );
                        } else {
                            // Access token failed, logout
                            self.loginService.invalidateSession();
                            // Emit UserSessionExpiredError
                            return throwError(new UserSessionExpiredError('refresh_token failed')); 
                        }
                    } else {
                        // Re-throw response error
                        return throwError(requestError);
                    }
                })
            );
        } else {
            return next.handle(req);
        }
    }

    /**
     * Check if request is required authentication.
     * @param req request
     */
    private checkUrl(req: HttpRequest<any>) {
        // Your logic to check if the request need authorization.
        return true;
    }
}

您可能需要檢查用戶是否啟用了“ Remember Me以使用刷新令牌進行重試或僅重定向到注銷頁面。

僅供參考, LoginService有以下方法:
- getAccessToken(): string - 返回當前的access_token
- isRememberMe(): boolean - 檢查用戶是否有refresh_token
- refreshToken(): Observable / Promise - 使用refresh_token向 oauth 服務器請求新的access_token
- invalidateSession(): void - 刪除所有用戶信息並重定向到注銷頁面

理想情況下,您希望在發送請求之前檢查isTokenExpired 如果過期刷新令牌並在標題中添加刷新。

除了retry operator可能有助於您在 401 響應上刷新令牌的邏輯。

在您發出請求的服務中使用RxJS retry operator 它接受一個retryCount參數。 如果未提供,它將無限期地重試序列。

在響應攔截器中刷新令牌並返回錯誤。 當您的服務返回錯誤但現在正在使用重試運算符時,它將重試請求,這次使用刷新的令牌(攔截器使用刷新的令牌添加到標頭中。)

import {HttpClient} from '@angular/common/http';
import { Injectable } from '@angular/core';
import { Observable } from 'rxjs/Rx';

@Injectable()
export class YourService {

  constructor(private http: HttpClient) {}

  search(params: any) {
    let tryCount = 0;
    return this.http.post('https://abcdYourApiUrl.com/search', params)
      .retry(2);
  }
}

在安德烈·奧斯特洛夫斯基 (Andrei Ostrovski) 最接受的答案中,人們評論了令牌刷新請求由於某種原因失敗時的內存泄漏。 可以通過使用 RxJS 超時運算符來緩解這種情況,如下所示:

//...

 tokenRefreshTimeout = 60000;

//...

    // Invalid token error
            else if (error.status === 401) {
                return this.refreshToken().pipe(
                    timeout(this.tokenRefreshTimeout), //added timeout here
                    switchMap(() => {
                        request = this.addAuthHeader(request);
                        return next.handle(request);
                    }),
//...

(對不起,我沒有足夠的代表來評論,我也不能建議編輯,因為編輯隊列總是滿的)

To support ES6 syntax the solution needs to be bit modify and that is as following also included te loader handler on multiple request


        private refreshTokenInProgress = false;
        private activeRequests = 0;
        private tokenRefreshedSource = new Subject();
        private tokenRefreshed$ = this.tokenRefreshedSource.asObservable();
        private subscribedObservable$: Subscription = new Subscription();



 intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
        if (this.activeRequests === 0) {
            this.loaderService.loadLoader.next(true);
        }
        this.activeRequests++;

        // Handle request
        request = this.addAuthHeader(request);

        // NOTE: if the flag is true it will execute retry auth token mechanism ie. by using refresh token it will fetch new auth token and will retry failed api with new token
        if (environment.retryAuthTokenMechanism) {
            // Handle response
            return next.handle(request).pipe(
                catchError(error => {
                    if (this.authenticationService.refreshShouldHappen(error)) {
                        return this.refreshToken().pipe(
                            switchMap(() => {
                                request = this.addAuthHeader(request);
                                return next.handle(request);
                            }),
                            catchError(() => {
                                this.authenticationService.setInterruptedUrl(this.router.url);
                                this.logout();
                                return EMPTY;
                            })
                        );
                    }

                    return EMPTY;
                }),
                finalize(() => {
                    this.hideLoader();
                })
            );
        } else {
            return next.handle(request).pipe(
                catchError(() => {
                    this.logout();
                    return EMPTY;
                }),
                finalize(() => {
                    this.hideLoader();
                })
            );
        }
    }

    ngOnDestroy(): void {
        this.subscribedObservable$.unsubscribe();
    }

    /**
     * @description Hides loader when all request gets complete
     */
    private hideLoader() {
        this.activeRequests--;
        if (this.activeRequests === 0) {
            this.loaderService.loadLoader.next(false);
        }
    }

    /**
     * @description set new auth token by existing refresh token
     */
    private refreshToken() {
        if (this.refreshTokenInProgress) {
            return new Observable(observer => {
                this.subscribedObservable$.add(
                    this.tokenRefreshed$.subscribe(() => {
                        observer.next();
                        observer.complete();
                    })
                );
            });
        } else {
            this.refreshTokenInProgress = true;

            return this.authenticationService.getNewAccessTokenByRefreshToken().pipe(tap(newAuthToken => {
            this.authenticationService.updateAccessToken(newAuthToken.access_token);
            this.refreshTokenInProgress = false;
            this.tokenRefreshedSource.next();
        }));
        }
    }

    private addAuthHeader(request: HttpRequest<any>) {
        const accessToken = this.authenticationService.getAccessTokenOnly();
        return request.clone({
            setHeaders: {
                Authorization: `Bearer ${accessToken}`
            }
        });
    }

    /**
     * @todo move in common service or auth service once tested
     * logout and redirect to login
     */
    private logout() {
        this.authenticationService.removeSavedUserDetailsAndLogout();
    }

我的答案

在這種情況下,只是處理程序 401

@Injectable()
export class AuthHttpInterceptor implements HttpInterceptor {

  logoutUser$ = defer(() => (this.authService.logout(), EMPTY));
  refresh$ = defer(() => this.authService.refreshTokenFromServer()).pipe(catchError(() => this.logoutUser$), share());

  constructor(private authService: AuthService) { }

  private applyCredentials(request: HttpRequest<any>): HttpRequest<any> {
    return request.clone({
      setHeaders: { Authorization: 'Bearer ' + this.authService.accessToken }
    });
  }

  public intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
    if (InterceptorSkipHeader.checkHeader(request)) {
      const req = InterceptorSkipHeader.deleteHeader(request);
      return next.handle(req);
    }
    const nextHandle$ = defer(() => next.handle(this.applyCredentials(request)));
    return iif(() => this.authService.tokenIsEmpty, this.logoutUser$, nextHandle$).pipe(this.httpErrorsHandler());
  }

  httpErrorsHandler() {
    return (source$: Observable<any>) => source$.pipe(
      catch401Error(() => this.handle401Error(source$)),
      catch400Error((err) => EMPTY),
      catch403Error((err) => EMPTY),
      catch406Error((err) => EMPTY),
      catch500Error((err) => EMPTY),
    );
  }

  handle401Error(retry$: Observable<any>): Observable<any> {
    return retry$.pipe(
      startWhen(this.refresh$),
      takeUntil(this.authService.logout$),
      catch401Error(() => this.logoutUser$),
    );
  }
}

完整代碼( auth-http-interceptor.ts )

step 1,創建兩個 Observable

logoutUser$

  • 使用defer()執行您的注銷邏輯(例如從 LocalStorage 清除令牌)並重新調整EMPTY

refresh$

  • 使用 defer create refresh$ Observable,使其始終使用新的刷新令牌來調用刷新 API

  • 捕獲錯誤時注銷

  • share()這個 Observable(讓所有 401 等待相同的刷新 API 返回)

logoutUser$ = defer(() => (this.authService.logout(), EMPTY));
refresh$ = defer(() => this.authService.refreshTokenFromServer()).pipe(catchError(() => this.logoutUser$), share());

第 2 步,跳過攔截器

只需讓 api 跳過攔截器( uitls.ts

class Xheader {
  static readonly interceptorSkipHeader = new Xheader('interceptorSkipHeader');

  readonly headers = { [this.headerName]: this.headerName };
  readonly options = { headers: this.headers };

  private constructor(readonly headerName: string) { }

  public checkHeader({ headers }: HttpRequest<any>) {
    return headers.has(this.headerName);
  }

  public deleteHeader(request: HttpRequest<any>) {
    return request.clone({ headers: request.headers.delete(this.headerName) });
  }
}

export const InterceptorSkipHeader = Xheader.interceptorSkipHeader;

像這樣InterceptorSkipHeader.options ( auth.service.ts )

refreshTokenFromServer(): Observable<Token> {
    return this.http.post<Token>(this.authApi + '/refreshToken', this.token, InterceptorSkipHeader.options).pipe(setTokenToLocalStorage());
}

第三步,攔截器

攔截器流程圖

已跳過 header InterceptorSkipHeader.checkHeader(request)

  • 刪除並返回沒有處理程序

否則,處理程序

  1. 使用訪問令牌創建nextHandle$applyCredentials(request)使用defer() (始終采用新的訪問令牌)
  2. 使用iif()檢查令牌是否為空將logoutUser$ ,否則nextHandle$
  3. 添加httpErrorsHandler()運算符,處理此 stream
public intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
    if (InterceptorSkipHeader.checkHeader(request)) {
      const req = InterceptorSkipHeader.deleteHeader(request);
      return next.handle(req);
    }
    const nextHandle$ = defer(() => next.handle(this.applyCredentials(request)));
    return iif(() => this.authService.tokenIsEmpty, this.logoutUser$, nextHandle$).pipe(this.httpErrorsHandler());
  }

添加訪問令牌 function

private applyCredentials(request: HttpRequest<any>): HttpRequest<any> {
    return request.clone({
      setHeaders: { Authorization: 'Bearer ' + this.authService.accessToken }
    });
  }

第四步,自定義算子

我們應該在錯誤處理程序之前創建一些自定義運算符

catchHttpError運算符

在這種情況下,我們只是處理 401

  • catch401Error : 捕獲 http 401
  • catch400Error : 捕獲 http 400
  • catch403Error : 捕獲 http 403
  • catch406Error : 捕獲 http 406
  • catch500Error : 捕獲 http 500
function catchHttpError(...status: Array<number>) {
  const statusMap = status.reduce((m, v) => m.set(v, v), new Map());
  return (next: (err: HttpErrorResponse) => Observable<any>) => {
    return catchError((err) => err instanceof HttpErrorResponse && statusMap.has(err.status) ? next(err) : throwError(err));
  };
}

const catch401Error = catchHttpError(401);
const catch400Error = catchHttpError(400);
const catch403Error = catchHttpError(403);
const catch406Error = catchHttpError(406);
const catch500Error = catchHttpError(500);

startWhen運算符 ( uitls.ts )

等於delayWhen()第二個參數(subscriptionDelay)

export function startWhen<T>(subscriptionDelay: Observable<any>) {
  return (source$: Observable<T>) => concat(subscriptionDelay.pipe(take(1), ignoreElements()), source$);
}

第 5 步,Http 錯誤處理程序

HttpErrorsHandler 流程圖

在這種情況下,我們只是處理 401

catch401Error 必須是第一個(確保其他錯誤處理程序將捕獲重試 API 錯誤)

  • handle401Error(source$)將重試source$ (previous Observable)
httpErrorsHandler() {
  return (source$: Observable<any>) => source$.pipe(
    catch401Error(() => this.handle401Error(source$)),
    catch400Error((err) => EMPTY),
    catch403Error((err) => EMPTY),
    catch406Error((err) => EMPTY),
    catch500Error((err) => EMPTY),
  );
}

處理401錯誤

  • startWhen() : retry$將等待refresh$完成而不是調用 retry API
  • 在處理過程中,如果authService.logout$觸發器將停止 stream(取消訂閱)
  • 如果重試 API 仍然 401 錯誤將注銷用戶
handle401Error(retry$: Observable<any>): Observable<any> {
  return retry$.pipe(
    startWhen(this.refresh$),
    takeUntil(this.authService.logout$),
    catch401Error(() => this.logoutUser$),
  );
}

https://medium.com/@eddylin1937/angular-interceptor-with-rxjs-refresh-token-176326c84a36

After api failed with HTTP Error 401,token-refresh api got called, all your failed and cached request can be retried using http interceptor.

if (this.isRefreshingToken && !req.url.endsWith(tokenURL)) {
      // check if unique url to be added in cachedRequest

      if (urlPresentIndex == -1) {
        this.cachedRequests.push(req);
        return this.tokenSubject.pipe(
          switchMap(() => next.handle(req)),
          tap((v) => {
            // delete request from catchedRequest if api gets called

            this.cachedRequests.splice(
              this.cachedRequests.findIndex(
                (httpRequest) => httpRequest.url == req.url
              ),
              1
            );
            return EMPTY;
          })
        );
      } else {
        //already in cached request array

        return EMPTY;
      }
    }

更多細節可以閱讀我的中篇文章Token-Refresh-Interceptor-retry-failed-Requests

我得到了這個基於失敗請求的 url 創建一個新請求並發送失敗請求的相同主體。

 retryFailedRequests() {

this.auth.cachedRequests.forEach(request => {

  // get failed request body
  var payload = (request as any).payload;

  if (request.method == "POST") {
    this.service.post(request.url, payload).subscribe(
      then => {
        // request ok
      },
      error => {
        // error
      });

  }
  else if (request.method == "PUT") {

    this.service.put(request.url, payload).subscribe(
      then => {
       // request ok
      },
      error => {
        // error
      });
  }

  else if (request.method == "DELETE")

    this.service.delete(request.url, payload).subscribe(
      then => {
        // request ok
      },
      error => {
        // error
      });
});

this.auth.clearFailedRequests();        

}

在您的 authentication.service.ts 中,您應該將 HttpClient 作為依賴項注入

constructor(private http: HttpClient) { }

然后您可以重新提交請求(在 retryFailedRequests 中),如下所示:

this.http.request(request).subscribe((response) => {
    // You need to subscribe to observer in order to "retry" your request
});

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM