繁体   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