使用HttpInterceptor

时间:2018-06-21 18:49:55

标签: javascript angular rxjs

我正在开发一个Ionic应用程序,并试图在用户收到HTTP请求的401响应时兑现刷新令牌。我发现了一些在线示例,并且可以使这个示例(https://www.intertech.com/Blog/angular-4-tutorial-handling-refresh-token-with-new-httpinterceptor/)正常工作,但同时有多个请求。

我遇到的问题是一系列调用中的第一个调用会调用刷新令牌并成功重试,而其他调用则永远不会重试。如果我在进行刷新的请求中使用.filter并删除.sub主题返回,则将重试调用,但不会使用新令牌。关于可观察性和主题,我还很陌生,所以我不太确定问题可能出在哪里。

请求

  this.myService.getData().subscribe(response => {this.data = response.data;}); 
  this.myService.getMoreData().subscribe(response => {this.moreData = response.data;}); 
  this.myService.getEvenMoreData().subscribe(response => {this.evenMoreData = response.data;}); 

拦截器

@Injectable()
export class HttpInterceptor implements HttpInterceptor {

  isRefreshingToken: boolean = false;
  tokenSubject = new BehaviorSubject<string>(null);   

  tokenService: tokenService;

  constructor(private authService: AuthService, private injector: Injector) { }

  intercept(request: HttpRequest<any>, next: HttpHandler): Observable<any> {

    return this.authService.getUser().flatMap(user => {
      request = this.addToken(request, next, user.accessToken);

      return next
        .handle(request)
        .catch(error => {
          if (error instanceof HttpErrorResponse) {
            switch ((<HttpErrorResponse>error).status) {
              case 401:
                return this.handle401(request, next, user);
            }
          } else {
          return Observable.throw(error);
        };
      })
    });


  }

  addToken(request: HttpRequest<any>, next: HttpHandler, accessToken: string): HttpRequest<any> {
    return request.clone({ setHeaders: { Authorization: 'Bearer ' + accessToken }})
  }

  handle401(request: HttpRequest<any>, next: HttpHandler, user: any) {

    if (!this.isRefreshingToken) {
      this.isRefreshingToken = true;
      this.tokenSubject.next(null);
      this.tokenService = this.injector.get(tokenService);
      return this.tokenService.refresh(user.refreshToken)
        .switchMap(refreshResponse => {
          if (refreshResponse) {
            this.authService.setUser(refreshResponse.id_token, refreshResponse.access_token, refreshResponse.refresh_token);
            this.tokenSubject.next(refreshResponse.accessToken);
            return next.handle(this.addToken(request, next, refreshResponse.access_token));
          }
          else {
             //no token came back. probably should just log user out.
          }
        })
        .finally(() => {
          this.isRefreshingToken = false;
      });      
    }
    else {
      return this.tokenSubject
        .filter(token => token != null)
        .take(1)
        .switchMap(token => {
          return next.handle(this.addToken(request, next, token));
        });
    }

  }



}

3 个答案:

答案 0 :(得分:0)

我过去也遇到过类似的问题。出于某种未知的原因(至少对我而言),当我拦截401时,我进行了刷新并重试,但是重试操作被取消了。

尽管如此,我意识到我可以在客户端读取JWT到期,因此我通过节省令牌到期时间来欺骗系统。然后,我进行了路由事件(例如onViewWillEnter)来检查到期时间,如果令牌已过期,则刷新它。

此机制对用户完全透明,可确保如果用户在不执行HTTP请求的情况下停留太长时间,则auth令牌或refresh令牌将过期,并且最重要的是,由于您永远不会获得401响应(因此在您的情况下,这会减少延迟) ,转化为三个请求。

实现此目标的一种简单方法是使用警卫:

canActivate(route: ActivatedRouteSnapshot,
  state: RouterStateSnapshot) {
    if (this.refreshTokenService.isExpired) {
        this.tokenEvent_.next();
        return false;
    } else {
        this.refreshTokenService.refresh();
    }

其中refreshTokenService是一种实用程序服务,它具有令牌以及通过HTTP执行刷新的方法。 tokenEventrxjs/Subject:它是在Guard构造函数中订阅的,每次出现新事件时,它都会重定向到登录页面。

在每条路由上添加此保护措施可确保令牌始终不会过期。

答案 1 :(得分:0)

实际上,通过将主题移至我的auth服务并在setUser方法中进行下一个操作,最终解决了这个问题。然后在401方法中的else语句中,我从auth服务上的新方法返回了主题,并对其进行了修复。我仍然需要take(1),但由于我最终没有使用BehaviorSubject,因此能够摆脱过滤器。

答案 2 :(得分:0)

在我看来,您的令牌不正确:

  • 您有:

    this.tokenSubject.next(refreshResponse.accessToken);

  • 应该是:

    this.tokenSubject.next(refreshResponse.access_token);