首页 > 解决方案 > 确保在发出下一个值之前发生订阅的最佳方法

问题描述

我正在使用 OAuth 2.0 在我的 Angular 应用程序中为我的用户检索令牌。我使用流行的库angular-oauth2-oidc来处理 OAuth 流。因此,在我的 AppComponent 中,我设置了 oAuthService angular-oauth2-oidc,然后通过调用在我的登录组件中启动 oAuth 流程initCodeFlow()。用户被重定向以输入他的凭据,然后他被重定向到我,我在 AppComponent 中收到令牌并将其作为下一个令牌放入我的 DataService TokenSubject。

但现在我想在 MainPageComponent 的标头中使用该令牌进行 API 调用。所以我在我的 MainPage 中订阅了 TokenSubject,当我收到 Token 时,订阅块中的代码就会执行。

但是,如果令牌 get 发送给我的速度比主页面构建的速度快怎么办?然后,当 AppComponent 发出下一个 Token 值时,我还没有订阅 Main Page 中的 TokenSubject。我不确定,但我的主页有时没有打开,我认为这可能是原因。

在发出 TokenSubject 之前,确保主页订阅 TokenSubject 的最佳方法是什么?

我的AppComponent

import { OAuthService } from 'angular-oauth2-oidc';
...

export class AppComponent implements OnInit {
    token: Token = {
      access_token: null,
      refresh_token: null
};

constructor(private oauthService: OAuthService,
                private adapterService: AdapterService,
                private dataService: DataService,
                private logger: NGXLogger) {
}

ngOnInit() {
        this.configureOAuthService();
        this.subscribeToOAuthEvents();
    }

configureOAuthService() {
        const authConfig: AuthConfig = {
            issuer: environment.oauthLoginUrl,
            redirectUri: environment.oauthRedirectUrl,
            clientId: environment.apiKey,
            scope: 'openid',
            responseType: 'code',
            showDebugInformation: true,
            disablePKCE: false,
            oidc: true,
            disableAtHashCheck: false,
        };

        this.oauthService.configure(authConfig);
        this.oauthService.tokenValidationHandler = new JwksValidationHandler();
        this.oauthService.loadDiscoveryDocumentAndTryLogin();
}

subscribeToOAuthEvents() {
        this.oauthService.events.subscribe(e => {
            if (e.type === 'token_received' || e.type === 'token_refreshed' ) {
                this.storeTokenInDataService();

                const self = this;
                // this will wait for 2 hours minus 30 sec before it refreshes the access token;
                setTimeout(() => { self.refreshToken(self); }, 7_170_000);
            } else if (e.type === 'token_expires') {
                this.logger.log('token_expires, going to refresh');
                this.refreshToken(this);
            } else if (e instanceof OAuthErrorEvent) {
                this.logger.error('OAuth error:', e);
            } else {
                this.logger.log('OAuthEvent:', e);
            }
        });
    }

    storeTokenInDataService() {
        this.token.access_token = this.oauthService.getAccessToken();
        this.token.refresh_token = this.oauthService.getRefreshToken();
        this.dataService.nextToken(this.token);
    }

在我的主页中,我订阅了 sharedToken 但我不确定在发出令牌之前是否总是发生这种情况:

ngOnInit(): void {
  console.log('going to get the token from dataService');
  this.dataService.sharedToken
    .pipe(
      takeUntil(this.destroy$),
      tap(token => {
        this.token = token;
        console.log('got Token');
      }),
      switchMap(token => {
        this.headers = this.createHeaders(token.access_token);

        return this.adapterService.getUserInfo(this.headers);
      }))
    .subscribe(UserInfo => {
      this.logger.log('received userInfo : ', userInfo);
      ... 
  });
 }
}

在我的 DataService 中,它看起来像这样:

  private token = new Subject<Token>();
  sharedPToken = this.token ;

  nextToken(token: Token) {
    this.token.next(token);
  }

更新:问题已解决,我的最终主要组件如下所示:

ngOnInit(): void {
  this.dataService.sharedTokenReplaySubject
    .pipe(
      takeLast(1),
      tap(token => {
        this.token = token;
        console.log('got Token');
      }),
      switchMap(token => {
        this.headers = this.createHeaders(token.access_token);

        return this.adapterService.getUserInfo(this.headers);
      }))
    .subscribe(UserInfo => {
      this.logger.log('received userInfo : ', userInfo);
      ... 
  });
 }
}

标签: angularrxjsangular-oauth2-oidc

解决方案


简而言之:你不能用Subject.

您可以改用 RxJS ReplaySubjectSubject它是您使用的更通用的变体。然而,它可以“保持/缓冲”特定数量的先前发射,并在订阅后立即发射给未来的订阅者。在您的情况下,您可以使用缓冲区 1。它将保存并发出推送给它的最后一个推送值。

private token = new ReplaySubject<Token>(1);  // <-- buffer 1
sharedToken = this.token.asObservable();

nextToken(token: Token) {
  this.token.next(token);
}

这样,您无需担心该值是推送到token订阅之前还是之后sharedToken。即使值在订阅之前被推送到,订阅this.dataService.sharedToken也会获得令牌token


推荐阅读