当前位置: 首页 > 知识库问答 >
问题:

Angular 5 HttpClient侦听器JWT刷新令牌无法捕获401并重试我的请求

洪飞白
2023-03-14

我正在尝试实现401响应的捕获,并尝试在令牌刷新后基于Angular 4 Intericetor重试请求获取刷新令牌。我试图实现同样的事情,但我始终无法重试该请求,我真的不确定这是否是应用刷新令牌策略的最佳方法。这是我的代码:

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

问题是在中从未到达切换映射...

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

共有2个答案

白子明
2023-03-14

下面的拦截器为您完成此任务

import {
  throwError as observableThrowError,
  Observable,
  Subject,
  EMPTY,
} from 'rxjs';

import { catchError, switchMap, tap, finalize } from 'rxjs/operators';
import { Injectable } from '@angular/core';
import {
  HttpInterceptor,
  HttpRequest,
  HttpHandler,
  HttpSentEvent,
  HttpHeaderResponse,
  HttpProgressEvent,
  HttpResponse,
  HttpUserEvent,
  HttpErrorResponse,
} from '@angular/common/http';
import { StoreService } from './store.service';
import { ApiService } from './api.service';

export const tokenURL = '/315cfb2a-3fdf-48c3-921f-1d5209cb7861'; //copied from api service

@Injectable()
export class SessionInterceptorService implements HttpInterceptor {
  isRefreshingToken: boolean = false;
  cachedRequests = [];
  tokenSubject: Subject<string> = new Subject<string>();

  constructor(
    private readonly store: StoreService,
    private readonly ApiService: ApiService
  ) {}

  intercept(
    req: HttpRequest<any>,
    next: HttpHandler
  ): Observable<
    | HttpSentEvent
    | HttpHeaderResponse
    | HttpProgressEvent
    | HttpResponse<any>
    | HttpUserEvent<any>
  > {
    let urlPresentIndex = this.cachedRequests.findIndex(
      (httpRequest) => httpRequest.url == req.url
    );

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

    return next.handle(this.updateHeader(req)).pipe(
      catchError((error) => {
        console.log(error);
        if (error instanceof HttpErrorResponse) {
          switch ((<HttpErrorResponse>error).status) {
            case 400:
              return this.handle400Error(error);
            case 403 || 401:
              if (req.url.endsWith(tokenURL)) {
                return observableThrowError(error);
              } else {
                this.cachedRequests.push(req);
                return this.handle401Error(req, next);
              }
            default:
              return observableThrowError(error);
          }
        } else {
          return observableThrowError(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.
      return this.logout();
    }

    return observableThrowError(error);
  }

  handle401Error(req: HttpRequest<any>, next: HttpHandler) {
    if (!this.isRefreshingToken) {
      this.isRefreshingToken = true;
      return this.ApiService.refreshToken().pipe(
        switchMap((newToken: string) => {
          if (newToken) {
            this.store.updateAccessToken(newToken);
            this.tokenSubject.next(newToken);
            return next.handle(this.updateHeader(this.cachedRequests[0]));
          }

          // If we don't get a new token, we are in trouble so logout.
          return this.logout();
        }),

        catchError((error) => {
          // If there is an exception calling 'refreshToken', bad news so logout.
          return this.logout();
        }),
        finalize(() => {
          this.isRefreshingToken = false;
        })
      );
    }
  }

  logout() {
    console.log('logging it out');
    // Route to the login page (implementation up to you)

    return observableThrowError('');
  }

  /*
        This method is append token in HTTP request'.
    */
  updateHeader(req) {
    const authToken = this.store.getAccessToken();
    console.log(authToken);
    req = req.clone({
      headers: req.headers.set('X-RapidAPI-Key', `${authToken}`),
    });
    return req;
  }
}

有关更多详细信息,您可以阅读我的中型文章令牌刷新侦听器重试失败的请求

看看它的工作原理

丌官运珧
2023-03-14

我很高兴你喜欢我的解决方案。我将把最终的解决方案放在这里,但是如果有人想知道我放弃的过程,请访问这里:刷新令牌OAuth身份验证Angular 4

好的,首先我创建了一个服务来保存刷新令牌请求的状态,并观察以知道请求何时完成。

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

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

我注意到,如果我有两个拦截器,一个用于刷新令牌并处理它,另一个用于放置授权标头(如果存在),那就更好了。

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

因此,我在这里等待刷新令牌可用或失败,然后释放需要授权头的请求。

@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,我认为应该有效。

 类似资料:
  • 您好,我正试图通过刷新令牌并重试请求,找出如何实现新的角度拦截器和处理错误。这是我一直遵循的指南:https://ryanchenkie.com/angular-authentication-using-the-http-client-and-http-interceptors 我成功缓存失败的请求,并可以刷新令牌,但我不知道如何重新发送以前失败的请求。我还想让它与我目前使用的解析器一起工作。 t

  • 正如标题所说,我正在使用OAuth身份验证进行Angular 4项目。 每当http请求以状态代码401响应时,我将拦截该请求,续订访问令牌并重试失败的请求。 当我收到401时,请求被正确截获,访问令牌被刷新。失败的请求也会再次执行,但不再将其响应传递给组件。 因此,问题在于,我的组件(应该在请求响应上进行观察)在刷新令牌和重试请求之前,会抱怨视图的日志“接收属性时出错”。 我的拦截器: 我的服务

  • 我正在构建一个移动应用程序,并且正在使用JWT进行身份验证。 最好的方法似乎是将JWT访问令牌与刷新令牌配对,这样我就可以根据需要频繁地使访问令牌过期。 刷新令牌是什么样子的?是随机字符串吗?那串加密了吗?是另一个JWT吗? 刷新令牌将存储在用户模型的数据库中以便访问,对吗?在这种情况下似乎应该加密 在用户登录后,我是否会将刷新令牌发送回,然后让客户端访问单独的路由来检索访问令牌?

  • 我在刷新访问令牌时得到这个错误:访问令牌无法刷新。请重新验证 这一错误此前曾在2017年4月报告过。OneLogin文档声明刷新令牌可以使用45天左右。我的刷新令牌已经有20个小时了。是文档正确还是刷新令牌的寿命更短?我可以做获取访问令牌和撤销令牌罚款。 public RootObject RefreshToken(HttpRequesterDM rDM){restsharp.deserializ

  • 我如何从第一次授权代码中获得刷新令牌和访问令牌?并且,我如何重用这个刷新令牌来获得一个新的访问令牌,以便使用Java API上传到Google Drive?这不是一个web应用程序。它在Java Swing代码中。

  • 我正在构建一个使用JWT进行身份验证的应用程序。我开始做一些研究,但对于诸如刷新令牌和令牌存储之类的主题缺乏共识,我感到惊讶。 据我所知,JWT和OAuth是两个不同的协议,它们遵循不同的规范。 但我的问题是,对于一个没有通过第三方资源服务器如Google、Facebook等认证的应用程序,有一个刷新令牌真的有用吗?为什么不让JWT令牌像刷新令牌一样持续时间长。 另一方面,我可以看到,如本文所述,