Angular 18中两个路由守卫交互引发无限重定向循环问题
路由守卫无限重定向循环问题排查与解决
配置了/和/auth两条路由,规则为未登录用户仅能访问/auth页面,已登录用户无法访问该页面。实现两个路由守卫后出现无限重定向循环,同时后端接口/auth/getUser无请求触发且超时。
现有代码配置
路由配置(app.routes.ts)
{ path: 'auth', canActivate: [OnlyUnauthorizedGuard], children: [ { path: '', component: AuthComponent, pathMatch: 'full' }, { path: 'login', component: LoginComponent }, { path: 'register', component: RegisterComponent }, ] }, { path: '', data: { allowedRoleIDs: [UserRole.Customer, UserRole.Owner, UserRole.Admin, UserRole.Root] }, canActivate: [RoleGuard], component: MainComponent, },
RoleGuard 守卫(role.guard.ts)
export class RoleGuard implements CanActivate { constructor(private authService: AuthService, private router: Router) { } canActivate(route: ActivatedRouteSnapshot): Observable<boolean> { console.log('Triggered RoleGuard'); const allowedRoles = route.data['allowedRoleIDs'] as number[]; return this.authService.user$.pipe( map(user => { console.log('user', user); if (user === null) { console.log('Redirecting to /auth since user is null'); this.router.navigate(['/auth']); return false; } if (user && !allowedRoles.includes(user.roleID)) { console.log('Redirecting to /404'); this.router.navigate(['/404']); return false; } return true; }) ); } }
OnlyUnauthorizedGuard 守卫(onlyUnauthorized.guard.ts)
export class OnlyUnauthorizedGuard implements CanActivate { constructor(private authService: AuthService, private router: Router) {} canActivate(): Observable<boolean> { console.log('Triggered OnlyUnauthorizedGuard'); return this.authService.user$.pipe( take(1), map(user => { console.log('user ', user); if (user) { console.log('User is logged in so redirecting to /'); this.router.navigate(['/']); return false; } console.log('Allowed'); return true; }) ); } }
AuthService 用户数据服务(auth.service.ts)
export class AuthService { private _userSubject = new BehaviorSubject<User | null>(null); user$ = this._userSubject.asObservable(); constructor( private http: HttpClient ) { this.loadUser(); } private loadUser() { this._loadingSubject.next(true); this.http.get<User>('/auth/getUser').pipe( tap(user => { this._userSubject.next(user) console.log(user) }), catchError(() => { this._userSubject.next(null); return of(null); }), finalize(() => this._loadingSubject.next(false)) ).subscribe(); } get user() { return this._userSubject.value; } }
异常现象
- 浏览器报错:
PM [vite] Internal server error: Page /auth/getUser did not render in 30 seconds.,网络面板无/auth/getUser请求 - Docker日志循环打印:
2024-10-28 00:26:06 Triggered RoleGuard 2024-10-28 00:26:06 user null 2024-10-28 00:26:06 Redirecting to /auth since user is null 2024-10-28 00:26:06 Triggered OnlyUnauthorizedGuard 2024-10-28 00:26:06 user null 2024-10-28 00:26:06 Allowed 2024-10-28 00:26:06 Triggered RoleGuard ...
问题根源
- 守卫重复触发:RoleGuard未添加
take(1),会持续监听user$的变化,每次用户状态更新都会重新执行守卫逻辑,引发重复重定向。 - 初始值误判:
_userSubject初始值为null,页面加载时守卫先于后端接口请求完成执行,直接判定未登录重定向,后续接口返回结果后又触发守卫,形成循环。 - 接口请求被拦截:前端路由
/auth与后端接口路径冲突,导致/auth/getUser被前端路由拦截,无法转发到后端。
解决方案
1. 修复守卫订阅逻辑,避免重复触发
给RoleGuard添加take(1),确保守卫只执行一次判断:
export class RoleGuard implements CanActivate { constructor(private authService: AuthService, private router: Router) { } canActivate(route: ActivatedRouteSnapshot): Observable<boolean> { console.log('Triggered RoleGuard'); const allowedRoles = route.data['allowedRoleIDs'] as number[]; return this.authService.user$.pipe( take(1), // 仅取一次用户状态,避免重复触发 map(user => { console.log('user', user); if (user === null) { console.log('Redirecting to /auth since user is null'); this.router.navigate(['/auth']); return false; } if (user && !allowedRoles.includes(user.roleID)) { console.log('Redirecting to /404'); this.router.navigate(['/404']); return false; } return true; }) ); } }
2. 等待用户数据加载完成后再执行守卫判断
在AuthService中添加加载状态,让守卫等待接口请求完成后再判断:
export class AuthService { private _userSubject = new BehaviorSubject<User | null>(null); private _loadingSubject = new BehaviorSubject<boolean>(true); user$ = this._userSubject.asObservable(); loading$ = this._loadingSubject.asObservable(); // 暴露加载状态 constructor(private http: HttpClient) { this.loadUser(); } private loadUser() { this._loadingSubject.next(true); this.http.get<User>('/auth/getUser').pipe( tap(user => { this._userSubject.next(user); console.log(user); }), catchError(() => { this._userSubject.next(null); return of(null); }), finalize(() => this._loadingSubject.next(false)) ).subscribe(); } get user() { return this._userSubject.value; } }
修改两个守卫,结合加载状态判断:
RoleGuard 修改:
import { combineLatest } from 'rxjs'; import { filter } from 'rxjs/operators'; export class RoleGuard implements CanActivate { constructor(private authService: AuthService, private router: Router) { } canActivate(route: ActivatedRouteSnapshot): Observable<boolean> { console.log('Triggered RoleGuard'); const allowedRoles = route.data['allowedRoleIDs'] as number[]; return combineLatest([this.authService.user$, this.authService.loading$]).pipe( filter(([_, loading]) => !loading), // 等待加载完成 take(1), map(([user]) => { console.log('user', user); if (user === null) { console.log('Redirecting to /auth since user is null'); this.router.navigate(['/auth']); return false; } if (user && !allowedRoles.includes(user.roleID)) { console.log('Redirecting to /404'); this.router.navigate(['/404']); return false; } return true; }) ); } }
OnlyUnauthorizedGuard 修改:
import { combineLatest } from 'rxjs'; import { filter } from 'rxjs/operators'; export class OnlyUnauthorizedGuard implements CanActivate { constructor(private authService: AuthService, private router: Router) {} canActivate(): Observable<boolean> { console.log('Triggered OnlyUnauthorizedGuard'); return combineLatest([this.authService.user$, this.authService.loading$]).pipe( filter(([_, loading]) => !loading), // 等待加载完成 take(1), map(([user]) => { console.log('user ', user); if (user) { console.log('User is logged in so redirecting to /'); this.router.navigate(['/']); return false; } console.log('Allowed'); return true; }) ); } }
3. 解决接口路径冲突问题
在vite配置中添加代理规则,确保API请求转发到后端:
// vite.config.ts export default defineConfig({ server: { proxy: { '/auth/getUser': { target: 'http://你的后端服务地址', changeOrigin: true, } } } });
内容的提问来源于stack exchange,提问作者ADEXITUM
相关产品推荐
相关产品推荐

