programing

Angular 4 토큰 새로 고침 후 가로채기 재시도 요청

skycolor 2023. 5. 17. 22:39
반응형

Angular 4 토큰 새로 고침 후 가로채기 재시도 요청

하세요. 하고 처리하는지 합니다.401 unauthorized토큰을 새로 고치고 요청을 다시 시도하면 오류가 발생합니다.이것이 제가 따라온 가이드입니다: https://ryanchenkie.com/angular-authentication-using-the-http-client-and-http-interceptors

실패한 요청을 성공적으로 캐싱하고 토큰을 새로 고칠 수 있지만 이전에 실패한 요청을 다시 보내는 방법을 알 수 없습니다.또한 현재 사용 중인 해결 프로그램에서 이 작업을 수행하고 싶습니다.

token.token.ts.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, 오류 처리 및 새로 고침 시 수정 루프로 코드가 업데이트됨토큰 실패

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)의 최신 버전을 사용하여 완벽하게 작동하는 Auto Session 복구 인터셉터를 생성하여 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 코멘트에 따르면, 저는 이 코드의 기능을 글로 설명하는 것이 좋은 생각이라고 생각했습니다.이 코드에 대한 설명과 이해(어떻게 작동하고 왜 작동하는지?)는 여기에 있는 제 기사를 읽어보실 수 있습니다.도움이 되길 바랍니다.

저는 다음과 같은 요구 사항을 해결해야 했습니다.

  • ✅ 여러 요청에 대해 토큰을 한 번만 새로 고침
  • ✅ 새로 고침 시 사용자 로그아웃토큰 실패
  • ✅ 처음 새로 고침 후 사용자에게 오류가 발생하면 로그아웃합니다.
  • ✅ 토큰을 새로 고치는 동안 모든 요청을 대기열에 넣습니다.

그 결과 Angular:의 토큰을 새로 고치기 위해 다양한 옵션을 수집했습니다.

  • 을 통한 무차별적인 솔루션tokenRefreshed$세마포어로서의 행동 주체
  • 매개 변수 사용catchError 실패 는 RxJS에서 요청 실패를 재시도합니다.
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);
      })
    );
}
  • RxJS 연산자 사용
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
      )),
    );
}

이 모든 옵션은 철저하게 테스트되었으며 각도 새로 고침 토큰 github repo에서 찾을 수 있습니다.

참고 항목:

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

저도 비슷한 문제에 부딪혔는데 수집/재시도 논리가 너무 복잡하다고 생각합니다.대신 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;
}

예를 바탕으로, 여기 제 작품이 있습니다.

@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에는 다음과 같은 방법이 있습니다.
getAccess는 - 큰(): 토다문 - 열니반현을 반환합니다.access_token
- 가 RememberMe()를 가지고 확인합니다. : 부울 - 사가있확는다지인니합자용▁is다확▁if있.refresh_token
refresh 파일: Token - new 파일: Tooauth / 약속에 - 신규tooauth 파일:access_token용사를 refresh_token
- 페이지 invalidSession(): void - 모든사정제보거를로고자페하니다합웃리션이로렉지디그아용▁log로 리디렉션합니다.

이상적으로, 당신이 확인하고 싶은 것은isTokenExpired 만료되면 토큰 고칩니다.만료된 경우 토큰을 새로 고치고 헤더에 새로 추가합니다.

그나저나나저.retry operator응답에 대한 고침 이 될 수 .401 답응토새고치로논리는도다될있니습수움이에큰을의▁may▁of.

을 합니다.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-proxy-proxy-proxy.ts)

1단계, 관측 가능한 두 개 생성

logoutUser$:

  • 사용하다defer() 로직을 수행하고 합니다.EMPTY

refresh$:

  • create 를 사용합니다.refresh$ 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());
}

3단계, 요격기

4

skip header 건기있 음더헤너InterceptorSkipHeader.checkHeader(request)

  • 핸들러 없이 삭제 및 반환

기타, 핸들러

  1. 를 만들다nextHandle$ 토큰 액세스큰포함:applyCredentials(request)사용하다defer() 새 토큰을
  2. 사용하다iif()하십시오.logoutUser$,또 다른nextHandle$
  3. 더하다httpErrorsHandler() 이 은 "", ""입니다.
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());
  }

액세스 토큰 기능 추가

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

4단계, 사용자 정의 연산자

오류 처리기 전에 사용자 지정 연산자를 만들어야 합니다.

catchHttpError 연산자

이 경우에는 401을 처리합니다.

  • catch401Error http 401 잡을 401 습다
  • catch400Error http 400도 400도입니다.
  • catch403Error http 403을 403으로 합니다.
  • catch406Error http 406http406을습다잡니
  • catch500Error http 500도 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);

시작 시간 연산자(utils.ts)

delayWhen()parameter ( (subscriptionDelay) 가 설정되어 .

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

5단계, Http 오류 처리기

5

이 경우에는 401을 처리합니다.

catch401Error는 첫 번째 오류여야 합니다(다른 오류 처리기가 재시도 API 오류를 탐지하는지 확인하십시오).

  • handle401Error(source$)다시 시도합니다.source$의 관측 가능이전관측
httpErrorsHandler() {
  return (source$: Observable<any>) => source$.pipe(
    catch401Error(() => this.handle401Error(source$)),
    catch400Error((err) => EMPTY),
    catch403Error((err) => EMPTY),
    catch406Error((err) => EMPTY),
    catch500Error((err) => EMPTY),
  );
}

handle401 오류

  • startWhen():retry$기다릴 것입니다refresh$보다 되었습니다.
  • 중인 ,authService.logout$트리거가 스트림을 중지합니다(가입 취소).
  • API를 다시 시도하면 401 오류가 발생하여 사용자가 로그아웃됩니다.
handle401Error(retry$: Observable<any>): Observable<any> {
  return retry$.pipe(
    startWhen(this.refresh$),
    takeUntil(this.authService.logout$),
    catch401Error(() => this.logoutUser$),
  );
}

https://medium.com/ @eddylin185/delin-delin-delin-delin-delin-with-delinjs-delin-delin-with-delin-delin-d

HTTP 오류 401로 api가 실패하고 토큰 새로 고침 api가 호출된 후, 실패하고 캐시된 모든 요청을 http 인터셉트를 사용하여 재시도할 수 있습니다.

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

자세한 내용은 제 중간 기사 토큰-새로 고침-인터셉터-재시도 실패-요청을 읽어보시기 바랍니다.

확인해보세요, 어떻게 작동하는지 stackblitz.

실패한 요청의 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();        

}

당신의 인증에서.service.ts, 종속성으로 HttpClient를 주입해야 합니다.

constructor(private http: HttpClient) { }

그런 다음 다음 다음과 같이 요청을 다시 제출할 수 있습니다(실패한 요청 내부에서 재시도).

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

언급URL : https://stackoverflow.com/questions/45202208/angular-4-interceptor-retry-requests-after-token-refresh

반응형