将 Bearer 令牌传递给另一个 APP_INITIALIZER 以从 Angular 应用程序中的服务器加载配置

Passing Bearer token to a different APP_INITIALIZER to load config from server in Angular App

我遇到了几个类似 , 的问题,但我不知道如何让我的应用正常运行。

问题: 当我第一次登录时,我没有得到 Bearer token,因此我的 SettingConfigService 失败并显示 401,如果我刷新页面,我从 this.oauth.getAccessToken() 获取令牌,因为现在令牌在 localstorage.

我正在使用 oauth lib 登录。这是我创建的模块和库。

App.module


export function AppConfigurationFactory(config: SettingConfigService) {
  return async () => await config.ensureInit(APP_NAME);
}

export class AppConfig {
  baseUrl: string;
  production: boolean;
}

export const appConfig: AppConfig = {
  baseUrl: environment.baseUrl,
  production: environment.production,
};

@NgModule({
  exports: [],
  declarations: [

  ],
  imports: [
     ....
    CustomAuthModule.forRoot(environment.keycloak),
    CustomInfrastructureModule.forRoot({ appConfig }),
    SharedModule,
  ],
  providers: [
    { provide: AppConfig, useValue: appConfig }, 
    ...
    {
      provide: APP_INITIALIZER,
      useFactory: AppConfigurationFactory,
      deps: [ SettingConfigService, HttpClient, TranslateService, OAuthService],
      multi: true,
    },
  ],
})
export class AppModule {}

CustomAuthModule.ts

import { NgModule, APP_INITIALIZER, Optional, SkipSelf, ModuleWithProviders, InjectionToken } from '@angular/core';
import { HttpClientModule } from '@angular/common/http';
import { OAuthModule, AuthConfig } from 'angular-oauth2-oidc';
import { OAuthModuleConfig,CustomAuthConfigParams } from './auth.config';
import { AuthConfigService } from './auth.service';

export function init_app(authConfigService: AuthConfigService) {
  return () => authConfigService.initAuth();
}

@NgModule({
  imports: [HttpClientModule, OAuthModule.forRoot()]
})
export classCustomAuthModule {
  constructor(@Optional() @SkipSelf() parentModule:CustomAuthModule){
    if(parentModule){
        throw new Error('QontrolAuthModule is already loaded.');
    }
  }

  static forRoot(keycloakParams): ModuleWithProviders<QontrolAuthModule> {
    return {
      ngModule:CustomAuthModule,      
      providers: [ 
        AuthConfigService,
        OAuthModuleConfig,
        { 
          provide: AuthConfig, 
          useValue: keycloakParams
        },
        {
          provide: APP_INITIALIZER,
          useFactory: init_app,
          deps: [AuthConfigService],
          multi: true,
        }, ]
    }
  }
}

CustomInfrastrucutureModule.ts

@NgModule({
  declarations: [],
  imports: [CommonModule],
  exports: [],
  providers: [],
})
export class CustomInfrastructureModule {

  static forRoot(conf?: {
    appConfig: SharedInfrastructureAppConfig;
  }): ModuleWithProviders<CustomInfrastructureModule> {
    return {
      ngModule: CustomInfrastructureModule,
      providers: [
        { provide: APP_CONFIG, useValue: conf.appConfig },
        {
          provide: LOCALE_ID,
          deps: [SettingConfigService], // some service handling global settings
          useFactory: (config: SettingConfigService) => config.culture
        },
      ],
    };
  }
}

设置配置服务


@Injectable({providedIn: 'root'})
export class SettingConfigService {
  culture: string;
  config: any;

  constructor(
    private httpClient: HttpClient,
    @Inject(APP_CONFIG) protected appConfig: SharedInfrastructureAppConfig,
    private oauth: OAuthService
  ) { }

  async ensureInit(clientPrefix: string): Promise<void>{
    console.log(this.oauth.getAccessToken());  //<-- comes as null when 1st login    
   // putting a wait time of 1 sec as per  makes it work,
     // because by that time, we have the token in localStorage
    const response = await this.httpClient.get<any>(`${this.appConfig.baseUrl}/configs`).toPromise();
    this.config = response;
  }
}

这是我的代码,它使用从

调用的 oauth2-oidc 获取令牌

AuthConfigService

  async initAuth(): Promise<any> {
    return new Promise((resolveFn, rejectFn) => {
      this.oauthService.configure(this.authConfig);
      // Redirect to path, if there is one
      if (window && window.location && window.location.pathname) {
        this.oauthService.redirectUri = window.location.origin + window.location.pathname;
      }

      this.oauthService.setStorage(localStorage);
      this.oauthService.tokenValidationHandler = new NullValidationHandler();

      this.oauthService.events
        .pipe(
          filter((e: any) => {
            return e.type === 'token_received';
          })
        )
        .subscribe(() => 
           this.handleNewToken() // <-- this takes time to get triggered and meanwhile
                       // the call to SettingConfigService is made
        );

      this.oauthService.loadDiscoveryDocumentAndLogin().then((isLoggedIn) => {
        if (isLoggedIn) {
          this.oauthService.setupAutomaticSilentRefresh();
          resolveFn(() => {});
        } else {
          console.log(this.oauthService.getAccessToken())
          this.oauthService.initImplicitFlow();
          console.log(this.oauthService.getAccessToken())
          rejectFn();
        }
      });
    });
  }

简而言之,

我需要同步 app.module.tsAPP_INITIALIZER 来等待 CustomAuthModuleAPP_INITIALIZER 的令牌,然后它将有一个不记名令牌(被添加通过拦截器)。我的理解正确吗?请帮忙

您需要按照正确的顺序使用令牌加载配置。尝试:

app.module

@NgModule({
  exports: [],
  declarations: [

  ],
  imports: [
     ....
    CustomAuthModule.forRoot(environment.keycloak, clientPrefixConstant),
    CustomInfrastructureModule.forRoot({ appConfig }),
    SharedModule,
  ],
  providers: [
    { provide: AppConfig, useValue: appConfig }, 
    ...
  ],
})
export class AppModule {}

在 AuthModule


export const CLIENT_NAME = new InjectionToken<string>('CLIENT_PARAM');

export function init_app(authConfigService: AuthConfigService,
    settingSvc: SettingConfigService,
    clientPrefix: string 
   ) {
  return () => authConfigService.initAuth().then(async () => {
      await settingSvc.ensureInit(clientName);
    });
}

@NgModule({
  imports: [HttpClientModule, OAuthModule.forRoot()]
})
export classCustomAuthModule {
  ....

  static forRoot(keycloakParams, clientPrefix): ModuleWithProviders<QontrolAuthModule> {
    return {
      ngModule:CustomAuthModule,      
      providers: [ 
        AuthConfigService,
        OAuthModuleConfig,
        { 
          provide: AuthConfig, 
          useValue: keycloakParams
        },
        {
          provide: CLIENT_NAME,
          useValue: clientPrefix
        },
        {
          provide: APP_INITIALIZER,
          useFactory: init_app,
          deps: [AuthConfigService,SettingConfigService,CLIENT_NAME],
          multi: true,
        }, ]
    }
  }
}

您的代码中发生的情况是 APP_INITIALIZER 被并行调用,这导致令牌不可用。根据我建议的答案,您首先解析令牌,然后调用 ensureInit。使用 InjectionToken,我接受了调用该函数所需的 string 值。