web-dev-qa-db-ja.com

Angular 5 HttpClient Interceptor JWTリフレッシュトークンが401をキャッチしてリクエストを再試行できない

私は401応答のキャッチを実装しようとしていて、 トークンリフレッシュ後のAngular 4 Interceptor再試行リクエスト に基づいてリフレッシュトークンを取得しようとしました。私は同じことを実装しようとしていましたが、その要求を再試行することができなかったので、それが更新トークン戦略を適用するための最良のアプローチであるかどうかは本当にわかりません。これが私のコードです:

@Injectable()
export class AuthInterceptorService implements HttpInterceptor {
 public authService;
 refreshTokenInProgress = false;
 tokenRefreshedSource = new Subject();
 tokenRefreshed$ = this.tokenRefreshedSource.asObservable();
 constructor(private router: Router, private injector: Injector) { }
 authenticateRequest(req: HttpRequest<any>) {
 const token = this.authService.getToken();
 if (token != null) {
 return req.clone({
 headers: req.headers.set('Authorization', `Bearer ${token.access_token}`)
 });
 }
 else {
 return null;
 }
 }
 refreshToken() {
 if (this.refreshTokenInProgress) {
 return new Observable(observer => {
 this.tokenRefreshed$.subscribe(() => {
 observer.next();
 observer.complete();
 });
 });
 } else {
 this.refreshTokenInProgress = true;

 return this.authService.refreshToken()
 .do(() => {
 this.refreshTokenInProgress = false;
 this.tokenRefreshedSource.next();
 }).catch(
 (error) => {
 console.log(error);
 }
 );
 }
 }
 intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
 this.authService = this.injector.get(AuthenticationService);
 request = this.authenticateRequest(request);
 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) {
 return this.refreshToken()
 .switchMap(() => {
 request = this.authenticateRequest(request);
 console.log('*Repeating httpRequest*', request);
 return next.handle(request);
 })
 .catch(() => {
 return Observable.empty();
 });
 }
 }
 });
 }
}

問題は、SwitchMapに到達しないことです...

if (err.status === 401) {
 return this.refreshToken()
 .switchMap(() => {

そしてdo演算子も...

return this.authService.refreshToken()
 .do(() => {

authService refreshTokenメソッドに連れて行ってくれました...

refreshToken() {
 let refreshToken = this.getToken();

 refreshToken.grant_type = 'refresh_token';
 refreshToken.clientId = environment.appSettings.clientId;
 return this.apiHelper.httpPost(url, refreshToken, null)
 .map
 (
 response => {
 this.setToken(response.data, refreshToken.email);
 return this.getToken();
 }
 ).catch(error => {

 return Observable.throw('Please insert credentials');
 });
 }
 }

それはマップされたオブザーバブルを返し、do inを置き換えた場合はサブスクリプションが必要であることを知っています...

return this.authService.refreshToken()
 .do(() => {

サブスクライブで、私が推測する観察可能なチェーンを壊します。私は迷子になっていて、これを解決策なしで長い間使ってきました。 :D

5
Mo Bdair

あなたが私の解決策を気に入ってくれてうれしいです。ここでは最終的な解決策のみを示しますが、誰かが私が許可していないプロセスを知りたい場合は、ここに行きます: Refresh Token OAuth Authentication Angular 4 +

では、最初にrefresh token requestとObservableの状態を保存するサービスを作成し、リクエストがいつ完了したかを確認しました。

これは私のサービスです:

@Injectable()
export class RefreshTokenService {
  public processing: boolean = false;
  public storage: Subject<any> = new Subject<any>();

  public publish(value: any) {
    this.storage.next(value);
  }
}

トークンを更新してそれを処理するインターセプターが2つ、承認ヘッダーが存在する場合はそれを配置するインターセプターが2つある方がよいことに気付きました。

これは、トークンを更新するためのインターセプターです。

@Injectable()
  export class RefreshTokenInterceptor implements HttpInterceptor {

    constructor(private injector: Injector, private tokenService: RefreshTokenService) {
    }

    intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
      const auth = this.injector.get(OAuthService);
      if (!auth.hasAuthorization() && auth.hasAuthorizationRefresh() && !this.tokenService.processing && request.url !== AUTHORIZE_URL) {
        this.tokenService.processing = true;
        return auth.refreshToken().flatMap(
          (res: any) => {
            auth.saveTokens(res);
            this.tokenService.publish(res);
            this.tokenService.processing = false;
            return next.handle(request);
          }
        ).catch(() => {
          this.tokenService.publish({});
          this.tokenService.processing = false;
          return next.handle(request);
        });
      } else if (request.url === AUTHORIZE_URL) {
        return next.handle(request);
      }

      if (this.tokenService.processing) {
        return this.tokenService.storage.flatMap(
          () => {
            return next.handle(request);
          }
        );
      } else {
        return next.handle(request);
      }
    }
  }

したがって、ここでは更新トークンが利用可能になるか失敗するのを待ってから、Authorizationヘッダーを必要とするリクエストを解放します。

これは、承認ヘッダーを配置するインターセプターです。

@Injectable()
  export class TokenInterceptor implements HttpInterceptor {
    constructor(private injector: Injector) {}

    intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
      const auth = this.injector.get(OAuthService);
      let req = request;
      if (auth.hasAuthorization()) {
        req = request.clone({
          headers: request.headers.set('Authorization', auth.getHeaderAuthorization())
        });
      }

      return next.handle(req).do(
        () => {},
        (error: any) => {
          if (error instanceof HttpErrorResponse) {
            if (error.status === 401) {
              auth.logOut();
            }
          }
        });
    }
  }

そして私のメインモジュールは次のようなものです:

@NgModule({
  imports: [
    ...,
    HttpClientModule
  ],
  declarations: [
    ...
  ],
  providers: [
    ...
    OAuthService,
    AuthService,
    RefreshTokenService,
    {
      provide: HTTP_INTERCEPTORS,
      useClass: RefreshTokenInterceptor,
      multi: true
    },
    {
      provide: HTTP_INTERCEPTORS,
      useClass: TokenInterceptor,
      multi: true
    }
  ],
  bootstrap: [AppComponent]
})
export class AppModule {
}

どんなフィードバックでも歓迎します、そして私が何か間違ったことをしているなら私に教えてください。 Angular 4.4.6でテストしていますが、angular 5で動作するかどうかはわかりませんが、動作するはずです。

5
Julio Guerra