将Bearer令牌传递到其他APP_INITIALIZER以从Angular App中的服务器加载配置

将Bearer令牌传递到其他APP_INITIALIZER以从Angular App中的服务器加载配置

问题描述:

我遇到了几个问题,例如 1 2 ,但我不知道如何使我的应用正常工作

I went through several questions like 1 , 2 but I don't know how to make my app work.

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

Problem: When I sign in 1st time, I do not get Bearer token and hence my SettingConfigService fails with 401, if I refresh the page, I get the token from this.oauth.getAccessToken() because now the token is in localstorage.

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

I am using oauth lib for login. Here is the modules and libs I have created.

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

SettingConfigService


@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 https://stackoverflow.com/questions/951021/what-is-the-javascript-version-of-sleep 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 取自

Here is my code which fetched the token using oauth2-oidc which is called from

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.ts APP_INITIALIZER ,以等待 CustomAuthModule APP_INITIALIZER 的令牌,然后将有一个承载令牌(由拦截器添加).我的理解正确吗?请帮助

In short,

I need to synchronize APP_INITIALIZER of app.module.ts to wait for token of APP_INITIALIZER of CustomAuthModule, and then it'll have a bearer token (which gets added by interceptor). Is my understanding correct? Please help

您需要按照正确的顺序来加载带有令牌的配置.尝试:

You need to follow the proper sequence to load config with the token. try:

应用模块

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

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

在AuthModule中

in 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 值.

What was happening in your code is that both the APP_INITIALIZER are called in parallel, which resulted in unavailability of token. With my suggested answer, you resolve the token 1st and then you call ensureInit. With InjectionToken,I accepted the string value which was required to call the function.