Angular认证后用APP_INITIALIZER加载用户角色遇依赖问题
你遇到的循环依赖问题,本质是APP_INITIALIZER启动阶段,HttpClient会触发TokenInterceptor,而拦截器依赖OidcSecurityService,同时BootstrapService又依赖HttpClient,形成了DI循环。第二种方案的报错则是因为OidcSecurityService的配置服务还未完成初始化,就提前尝试获取实例导致的。
下面提供两种经过验证的解决方案:
方案一:用HttpBackend绕过拦截器,配合APP_INITIALIZER确保角色加载完成
核心思路是:直接使用HttpBackend创建不经过拦截器的HttpClient实例,避免触发循环依赖;同时让loadUserRoles返回Promise/Observable,确保Angular等待角色加载完成后再初始化组件。
修改BootstrapService:
@Injectable() export class BootstrapService { roles: string; private readonly http: HttpClient; constructor( private httpBackend: HttpBackend, private oidcService: OidcSecurityService ) { // 直接基于HttpBackend创建HttpClient,跳过所有拦截器 this.http = new HttpClient(httpBackend); } loadUserRoles(): Promise<void> { return this.oidcService.checkAuth().pipe( // 先确认用户已认证 switchMap(authResult => { if (!authResult.isAuthenticated) { throw new Error('用户未完成认证,无法加载角色'); } return this.oidcService.getAccessToken(); }), // 携带Token调用后端接口 switchMap(token => { return this.http.get<string>('http://localhost:8080/api/roles', { headers: { Authorization: `Bearer ${token}` } }); }), // 保存角色信息 tap(roles => { this.roles = roles; console.log('Roles', this.roles); }), catchError(err => { console.error('加载角色失败:', err); return throwError(() => err); }), // 转换为Promise,满足APP_INITIALIZER的要求 toPromise() ); } }
调整APP_INITIALIZER工厂函数:
要确保工厂返回的函数能返回异步操作的结果,让Angular等待:
export function initRolesFactory(bootstrapService: BootstrapService): () => Promise<void> { return () => bootstrapService.loadUserRoles(); } // 在模块的providers数组中配置 { provide: APP_INITIALIZER, useFactory: initRolesFactory, deps: [BootstrapService], multi: true }
方案二:利用路由守卫+认证事件,延迟组件加载到角色获取完成
如果APP_INITIALIZER的时机不合适(比如认证流程在启动后才完成),可以用路由守卫控制页面访问,同时监听认证成功事件触发角色加载:
1. 实现角色路由守卫
@Injectable({ providedIn: 'root' }) export class AuthRoleGuard implements CanActivate { constructor( private router: Router, private bootstrapService: BootstrapService, private oidcService: OidcSecurityService ) {} canActivate(route: ActivatedRouteSnapshot): Observable<boolean> { return this.oidcService.checkAuth().pipe( switchMap(authResult => { // 未认证则跳转到登录页 if (!authResult.isAuthenticated) { this.router.navigate(['/login']); return of(false); } // 角色未加载则先加载 if (!this.bootstrapService.roles) { return this.bootstrapService.loadUserRoles().pipe( map(() => { // 校验当前角色是否允许访问路由 const allowedRoles = route.data['allowedRoles'] as string[]; const hasAccess = allowedRoles.includes(this.bootstrapService.roles); if (!hasAccess) this.router.navigate(['/access-denied']); return hasAccess; }), catchError(() => { this.router.navigate(['/error']); return of(false); }) ); } else { // 角色已加载,直接校验权限 const allowedRoles = route.data['allowedRoles'] as string[]; const hasAccess = allowedRoles.includes(this.bootstrapService.roles); if (!hasAccess) this.router.navigate(['/access-denied']); return of(hasAccess); } }) ); } }
2. 配置路由
在路由中添加守卫和角色权限:
const routes: Routes = [ { path: 'admin', component: AdminComponent, canActivate: [AuthRoleGuard], data: { allowedRoles: ['admin'] } }, { path: 'user', component: UserComponent, canActivate: [AuthRoleGuard], data: { allowedRoles: ['user'] } }, // 其他路由... ];
3. 修改BootstrapService并监听认证事件
@Injectable({ providedIn: 'root' }) export class BootstrapService { roles: string; private readonly http: HttpClient; constructor( private httpBackend: HttpBackend, private oidcService: OidcSecurityService ) { this.http = new HttpClient(httpBackend); } loadUserRoles(): Observable<string> { return this.oidcService.getAccessToken().pipe( switchMap(token => { return this.http.get<string>('http://localhost:8080/api/roles', { headers: { Authorization: `Bearer ${token}` } }); }), tap(roles => this.roles = roles) ); } }
在AppComponent中监听认证成功事件,触发角色加载:
@Component({ selector: 'app-root', template: '<router-outlet></router-outlet>' }) export class AppComponent implements OnInit { constructor( private oidcService: OidcSecurityService, private bootstrapService: BootstrapService ) {} ngOnInit(): void { // 初始化时检查认证状态 this.oidcService.checkAuth().subscribe(authResult => { if (authResult.isAuthenticated) { this.bootstrapService.loadUserRoles().subscribe(); } }); // 监听登录后的认证结果事件 this.oidcService.onAuthorizationResult.subscribe(authResult => { if (authResult.isAuthenticated) { this.bootstrapService.loadUserRoles().subscribe(); } }); } }
内容的提问来源于stack exchange,提问作者Cosmin D
相关产品推荐
相关产品推荐

