Angular项目用oidc认证时出现token未定义断言错误求助
Angular OIDC认证断言错误:ASSERTION ERROR: token must be defined
问题详情
项目采用OIDC(OpenID)实现身份认证,修改过bootstrap组件为MainRootComponent,第一个运行的组件是HomeMainComponent(包含OIDC核心逻辑),控制台抛出断言错误:
ASSERTION ERROR: token must be defined [Expected=> null != undefined <=Actual]

相关代码
Home-main 组件代码
import { Component, Injector, OnInit } from '@angular/core'; import { AppComponentBase } from '../utils'; import { StorageService } from '../services/storage.service'; import { Title } from '@angular/platform-browser'; import { UserManagementService } from '../pages/user-management/user-management.service'; import { AbpService } from '../config/abp/licalization.service'; import { OAuthService } from 'angular-oauth2-oidc'; import { DataService } from '../services/data.service'; import { environment } from 'src/environments/environment.prod'; import { filter, map } from 'rxjs'; import { authCodeFlowConfig } from '../config/authCodeFlowConfig'; import { IApplicationConfigurationDto } from '../models/identityTypes'; @Component({ selector: 'app-home-main', templateUrl: './home-main.component.html', styleUrls: ['./home-main.component.scss'], }) export class HomeMainComponent extends AppComponentBase implements OnInit { loadFirstLoginProject: boolean = false; favIcon: HTMLLinkElement = document.querySelector('#favIcon'); favIconAddress: string = environment.localization.favIcon_Project; constructor(inject: Injector, private storageService: StorageService, private titlePage: Title, private userManagementService: UserManagementService, private _abpService: AbpService, private dataService: DataService, private _oAuthService: OAuthService) { super(inject) this.getConfigProject(); this._oAuthService.configure(authCodeFlowConfig); this._oAuthService.loadDiscoveryDocumentAndLogin(); this._oAuthService.setupAutomaticSilentRefresh(); this._oAuthService.events.pipe(filter((e) => e.type === 'token_received')).subscribe((_) => { if (!this.loadFirstLoginProject) { window.location.reload(); } }); const lang = this.getCurrentLang(); if (lang === 'ku') { document.body.classList.add('ku-language'); } this.titlePage.setTitle(environment.localization.nameTitleProject) this.favIcon.href = this.favIconAddress; } getConfigProject() { if (this.storageService.retrieve('access_token')) { this.loadFirstLoginProject = true; } } public OnInit(): void { this.dataService.getApplication().pipe(map((response: IApplicationConfigurationDto) => { this.translate.setDefaultLang(response.setting.values['Abp.Localization.DefaultLanguage'].replaceAll('"', '')); this.translate.use(response.setting.values['Abp.Localization.DefaultLanguage'].replaceAll('"', '')); this.securityService.subjectUpdateUserByToken.next(response); this._abpService.init(response); this._abpService.setCurrentLang(response.setting.values['Abp.Localization.DefaultLanguage'].replaceAll('"', '')); sessionStorage.setItem('currentLang', response.setting.values['Abp.Localization.DefaultLanguage']); this.securityService.updateSubjectUserByToken(response); this.securityService.currentUser = response.currentUser; localStorage.setItem('photoUserImage', this.convertAddressImageUser('')); if (this._oAuthService.hasValidAccessToken()) { this.userManagementService.getAllUsers('', 0, 999).subscribe(x => { this.securityService.userList = x.items.map(x => { x.fullName = `${x.name} ${x.surname}` return x; }); this.securityService.currentUser = response.currentUser; }, error => { console.log(error) }); this.securityService.dataApplicationService = response; return true } return false; }, err => err)); } }
package.json 依赖配置
{ "private": true, "dependencies": { "@angular/animations": "^16.2.0", "@angular/common": "^16.2.0", "@angular/compiler": "^16.2.0", "@angular/core": "^16.2.0", "@angular/forms": "^16.2.0", "@angular/platform-browser": "^16.2.0", "@angular/platform-browser-dynamic": "^16.2.0", "@angular/router": "^16.2.0", "@ngx-translate/core": "^15.0.0", "@ngx-translate/http-loader": "^8.0.0", "angular-oauth2-oidc": "^15.0.1", "bootstrap": "^5.3.2", "font-awesome": "^4.7.0", "html-to-image": "^1.11.11", "html-to-pdfmake": "^2.5.1", "jalali-moment": "^3.3.11", "jspdf": "^2.5.1", "lodash": "^4.17.21", "moment": "^2.29.4", "ngx-persian": "^2.0.1", "ngx-spinner": "^16.0.2", "ngx-toastr": "^17.0.2", "primeicons": "^6.0.1", "primeng": "^16.9.1", "rxjs": "~7.8.0", "tslib": "^2.3.0", "zone.js": "~0.13.0" }, "devDependencies": { "@angular-devkit/build-angular": "^16.2.1", "@angular/cli": "~16.2.1", "@angular/compiler-cli": "^16.2.0", "@angular/localize": "^16.2.12", "@types/jasmine": "~4.3.0", "jasmine-core": "~4.6.0", "karma": "~6.4.0", "karma-chrome-launcher": "~3.2.0", "karma-coverage": "~2.2.0", "karma-jasmine": "~5.1.0", "karma-jasmine-html-reporter": "~2.1.0", "typescript": "~5.1.3" } }
app.module.ts 配置
import { NgModule } from '@angular/core'; import { BrowserModule } from '@angular/platform-browser'; import { AppRoutingModule } from './app-routing.module'; import { AppComponent } from './app.component'; import { MainRootComponent } from './main-root/main-root.component'; import { TranslateLoader, TranslateModule } from '@ngx-translate/core'; import { SharedModule } from './shared/shared.module'; import { APP_BASE_HREF } from '@angular/common'; import { HttpClient, HttpClientModule } from '@angular/common/http'; import { TranslateHttpLoader } from '@ngx-translate/http-loader'; import { StorageService } from './services/storage.service'; import { DataService } from './services/data.service'; import { SecurityService } from './services/security.service'; import { ToastrModule, ToastrService } from 'ngx-toastr'; import { OAuthModule } from 'angular-oauth2-oidc'; import { MessageService } from 'primeng/api'; @NgModule({ declarations: [ AppComponent, MainRootComponent, ], imports: [ BrowserModule, AppRoutingModule, SharedModule, HttpClientModule, ToastrModule.forRoot(), OAuthModule.forRoot(), TranslateModule.forRoot({ loader: { provide: TranslateLoader, useFactory: HttpLoaderFactory, deps: [HttpClient] } }), ], bootstrap: [MainRootComponent], providers: [ { provide: APP_BASE_HREF, useValue: '/' }, StorageService, ToastrService, MessageService, DataService, SecurityService ], }) export class AppModule { } export function HttpLoaderFactory(http: HttpClient): TranslateHttpLoader { return new TranslateHttpLoader(http); }
错误原因分析
- 生命周期钩子错误:组件中定义的
OnInit()方法不符合Angular规范,正确的生命周期钩子应为小写开头的ngOnInit(),导致初始化逻辑未被执行,OIDC认证与后续业务逻辑时序混乱。 - OIDC初始化时机过早:在构造函数中直接调用
loadDiscoveryDocumentAndLogin()和setupAutomaticSilentRefresh(),此时组件未完成初始化,可能在token未获取时触发依赖token的操作。 - HTTP请求未订阅:
dataService.getApplication()的Observable未调用subscribe(),导致请求未执行,后续依赖该接口返回值的逻辑无数据可用,间接引发token校验错误。 - 静默刷新逻辑冲突:
setupAutomaticSilentRefresh()在无有效token时尝试刷新,触发库内部的断言检查,抛出token未定义错误。
修复方案
1. 修正生命周期钩子
将public OnInit(): void改为public ngOnInit(): void,确保Angular能正确调用初始化逻辑。
2. 调整OIDC初始化时序
将OIDC相关逻辑从构造函数移到ngOnInit中,并在认证成功后再执行业务初始化:
constructor( inject: Injector, private storageService: StorageService, private titlePage: Title, private userManagementService: UserManagementService, private _abpService: AbpService, private dataService: DataService, private _oAuthService: OAuthService ) { super(inject); this.getConfigProject(); const lang = this.getCurrentLang(); if (lang === 'ku') { document.body.classList.add('ku-language'); } this.titlePage.setTitle(environment.localization.nameTitleProject); this.favIcon.href = this.favIconAddress; } ngOnInit(): void { // 配置OIDC this._oAuthService.configure(authCodeFlowConfig); // 加载发现文档并登录,认证成功后执行后续逻辑 this._oAuthService.loadDiscoveryDocumentAndLogin().then(() => { if (this._oAuthService.hasValidAccessToken()) { this.initApplicationConfig(); } }); this._oAuthService.setupAutomaticSilentRefresh(); this._oAuthService.events.pipe(filter(e => e.type === 'token_received')).subscribe(() => { if (!this.loadFirstLoginProject) { window.location.reload(); } }); } // 提取业务初始化逻辑,确保在认证成功后执行 private initApplicationConfig() { this.dataService.getApplication().pipe(map((response: IApplicationConfigurationDto) => { const defaultLang = response.setting.values['Abp.Localization.DefaultLanguage'].replaceAll('"', ''); this.translate.setDefaultLang(defaultLang); this.translate.use(defaultLang); this.securityService.subjectUpdateUserByToken.next(response); this._abpService.init(response); this._abpService.setCurrentLang(defaultLang); sessionStorage.setItem('currentLang', response.setting.values['Abp.Localization.DefaultLanguage']); this.securityService.updateSubjectUserByToken(response); this.securityService.currentUser = response.currentUser; localStorage.setItem('photoUserImage', this.convertAddressImageUser('')); if (this._oAuthService.hasValidAccessToken()) { this.userManagementService.getAllUsers('', 0, 999).subscribe({ next: x => { this.securityService.userList = x.items.map(item => ({ ...item, fullName: `${item.name} ${item.surname}` })); this.securityService.currentUser = response.currentUser; }, error: err => console.log(err) }); this.securityService.dataApplicationService = response; return true; } return false; })).subscribe(); // 必须调用subscribe才会执行HTTP请求 }
3. 检查OIDC配置
确认authCodeFlowConfig中的clientId、redirectUri、issuer等参数与OIDC服务端配置一致,错误配置会导致无法获取有效token。
4. 增加token校验防护
在所有依赖token的操作前,先通过this._oAuthService.hasValidAccessToken()校验token有效性,避免在无token时触发错误逻辑。
内容的提问来源于stack exchange,提问作者davood beheshti
相关产品推荐
相关产品推荐

