Angular AuthGuard未等待App组件ngOnInit执行完成就重定向到登录页如何解决
问题根因
Angular路由守卫的执行时机早于应用根组件的ngOnInit生命周期,同时你当前的实现存在两个逻辑漏洞:
- 调用
authService.setUser()时仅更新了currentUser变量,没有同步更新_isAuthenticated的状态值 AuthGuard直接读取_isAuthenticated的瞬时值,没有等待用户身份校验的接口返回结果
具体修复方案
步骤1:调整AuthService逻辑
首先修正setUser方法的状态同步,同时将用户身份初始化逻辑抽到服务内部,增加初始化状态标识:
import {Injectable} from '@angular/core'; import {User, MeQuery, MeDocument, MeQueryVariables} from "../generated/graphql"; import {BehaviorSubject, first, map, skipWhile} from "rxjs"; import {Apollo} from "apollo-angular"; export type CurrentUserType = Pick<User, 'id' | 'name' | 'email' | 'active' | 'type'> @Injectable({ providedIn: 'root' }) export class AuthService { private TOKEN_KEY = "AGENDEI_TOKEN" private currentUser: CurrentUserType | null = null // 初始值改为null,代表未初始化 private _isAuthenticated = new BehaviorSubject<boolean | null>(null); private authSource = new BehaviorSubject<CurrentUserType | null>(null) constructor(private apollo: Apollo) { // 服务初始化时就执行身份校验 this.initAuth() } // 新增身份初始化方法 async initAuth() { // 没有token直接标记为未登录 if(!localStorage.getItem(this.TOKEN_KEY)) { this._isAuthenticated.next(false) return } try { const {data} = await this.getUserFromMeQuery() if(data.me) { this.setUser(data.me) this._isAuthenticated.next(true) } else { this._isAuthenticated.next(false) } } catch (e) { this._isAuthenticated.next(false) } } loginUser(user: CurrentUserType, accessToken: string) { localStorage.setItem(this.TOKEN_KEY, accessToken) this.setUser(user) this._isAuthenticated.next(true) } setUser(user: CurrentUserType) { this.currentUser = user this.authSource.next(user) } async logout() { localStorage.removeItem(this.TOKEN_KEY) await this.apollo.getClient().resetStore() this.currentUser = null this.authSource.next(null) this._isAuthenticated.next(false); } public isAuthenticated() { return this._isAuthenticated.asObservable().pipe( // 跳过未初始化状态,等校验完成再返回结果 skipWhile(val => val === null), map(val => val as boolean), first() ) } public getUserFromMeQuery() { return this.apollo.query<MeQuery, MeQueryVariables>({ query: MeDocument, fetchPolicy: "network-only" }).toPromise() } }
步骤2:调整AuthGuard逻辑
修改canActivate方法,等待身份校验完成再判断是否放行:
import { Injectable } from '@angular/core'; import {ActivatedRouteSnapshot, CanActivate, Router, RouterStateSnapshot} from "@angular/router"; import {AuthService} from "../auth.service"; import {firstValueFrom} from "rxjs"; @Injectable({ providedIn: 'root' }) export class AuthGuardService implements CanActivate{ constructor(private authService: AuthService, private router: Router) { } async canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Promise<boolean > { console.log("Auth Guard user mount") const isAuth = await firstValueFrom(this.authService.isAuthenticated()) if(!isAuth) { console.log("Não autenticado") await this.router.navigate(['/login']) return false } return true } }
步骤3:简化App组件逻辑
现在身份初始化逻辑已经移到AuthService,App组件只需要监听初始化状态控制 loading 即可:
import {Component, OnInit} from '@angular/core'; import {AuthService} from "./auth.service"; import {firstValueFrom} from "rxjs"; @Component({ selector: 'app-root', templateUrl: 'app.component.html' }) export class AppComponent implements OnInit { title = 'frontend'; loading: boolean = true constructor(private authService: AuthService) { } async ngOnInit() { // 等待身份初始化完成再关闭loading await firstValueFrom(this.authService.isAuthenticated()) this.loading = false } }
可选优化:使用APP_INITIALIZER提前阻塞初始化
如果你希望在应用完全启动前就完成身份校验,可以在AppModule中添加APP_INITIALIZER provider,确保路由加载前身份状态已经确定:
// app.module.ts import { APP_INITIALIZER, NgModule } from '@angular/core'; import { AuthService } from './auth.service'; import { firstValueFrom } from 'rxjs'; export function initAuth(authService: AuthService) { return () => firstValueFrom(authService.isAuthenticated()); } @NgModule({ // ...其他配置 providers: [ { provide: APP_INITIALIZER, useFactory: initAuth, deps: [AuthService], multi: true } ] }) export class AppModule { }
内容的提问来源于stack exchange,提问作者Vinicius Barbosa de Medeiros
相关产品推荐
相关产品推荐

