在拦截器内部使用可观察对象

Use an observable inside of an interceptor

我想编写一个拦截器来为所有请求添加一个授权令牌。我的令牌来自一个库,angularx-social-login,它只提供一个 Observable 来获取令牌。所以我写了这个但是我的请求从未发送过,就好像这个值从未从可观察的输出中一样。

import { HttpEvent, HttpHandler, HttpInterceptor, HttpRequest} from "@angular/common/http";
import { SocialAuthService } from "angularx-social-login";
import {Observable, switchMap} from "rxjs";
import {Injectable} from "@angular/core";

@Injectable()
export class AuthInterceptorService implements HttpInterceptor {
  constructor(private authService: SocialAuthService) {}
  intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
    return this.authService.authState.pipe(
      switchMap((user) => {
        const token = user.idToken
        if (token) {
          request = request.clone({
            setHeaders: {Authorization: `Bearer ${token}`}
          });
        }
        return next.handle(request)
      })
    );
  }
}

我不是 100% 确定这一点,但我认为使用 switchMap 你正在切换到一个新的 observable,所以 return next.handle(request) 永远不会被下一个。似乎 authState 是一个可观察的,所以我们可以 pipe(take(1)) 而无需取消订阅。

我会更简洁,将获取令牌和设置 Bearer 分开。

import { HttpEvent, HttpHandler, HttpInterceptor, HttpRequest } from '@angular/common/http';
import { SocialAuthService } from 'angularx-social-login';
import { Observable } from 'rxjs';
import { Injectable } from '@angular/core';

@Injectable()
export class AuthInterceptorService implements HttpInterceptor {
  constructor(private authService: SocialAuthService) {}
  intercept( request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {

    let token;
    this.authService.authState.pipe(take(1)).subscribe(data => token = data);

    if (token) {
      // If we have a token, we set it to the header
      request = request.clone({
        setHeaders: { Authorization: `Bearer ${token}` },
      });
    }

    return next.handle(request).pipe(
      catchError((err) => {
        if (err instanceof HttpErrorResponse) {
          if (err.status === 401) {
            // redirect user to the logout page
          }
        }
        return throwError(err);
      })
    );
  }
}

还建议重构接收令牌的方式。就像将它更新到某个存储并通过 authService 从存储接收它一样。每次订阅一个 observable 是一件痛苦的事情。

AuthService.ts

getAuthToken():string {
 return localeStarage.getItem('token')
 }

然后你可以这样做:

const token = this.authService.getAuthToken();

我想这里发生的事情是,当订阅 observable 时,authState 的值已经在登录过程中发出,因此请求挂起等待发出新值,这已经发生了在登录过程中。

为了解决这个问题,我建议你实现一个服务(providedInRoot)注入到登录组件中,并在登录过程中检索用户数据。

您可以在 Login 组件的 OnInit 中订阅 SocialAuthService 服务的 authState observable:

import { Component, OnInit, OnDestroy } from '@angular/core';
import { SocialAuthService } from "angularx-social-login";
import { Subject } from 'rxjs';
import { takeUntil } from 'rxjs/operators';

// ... the rest of import

@Component({
  // ... Component decorator props (selector, templateUrl, styleUrls)
})
export class LoginComponent implements OnInit, OnDestroy {
  destroy = new Subject<boolean>();

  constructor(private authService: SocialAuthService, myUserService: MyUserService) { }

  ngOnInit(): void {
    this.authService.authState.pipe(takeUntil(this.destroy)).subscribe((user) => {
      this.myUserService.user = user;
    });
  }

  // ... the rest of the component

  ngOnDestroy(): void {
    this.destroy.next(true);
    this.destroy.complete();
  }

}

然后您可以在拦截器中使用 myUserService.user 中的值来检索令牌。

我已经将 takeUntil rxjs 运算符与 ngOnDestroy 的主题一起使用,但您也可以将订阅存储为 class 变量并执行取消订阅。

import { Component, OnInit, OnDestroy } from '@angular/core';
import { SocialAuthService } from "angularx-social-login";
import { Subscription } from 'rxjs';

// ... the rest of import

@Component({
  // ... Component decorator props (selector, templateUrl, styleUrls)
})
export class LoginComponent implements OnInit, OnDestroy {
  authStateSubscription: Subscription;

  constructor(private authService: SocialAuthService, myUserService: MyUserService) { }

  ngOnInit(): void {
    this.authStateSubscription = this.authService.authState.subscribe((user) => {
      this.myUserService.user = user;
    });
  }

  // ... the rest of the component

  ngOnDestroy(): void {
    this.authStateSubscription.unsubscribe();
  }

}

两种方式都应该有效。

我倾向于同意@cybering 的解释,要添加到他的解决方案中,您可以将 authState 定义为 BehaviorSubject。这样,无论某个组件何时订阅它,它都会获得一个发射值,因为 BehaviorSubject“保存”最后发射的值并在新订阅时发射它而不依赖于时间。