Angular Auth Service登录后报Location/History API调用过多错误
Angular模拟认证路由重定向报错问题
项目背景
正在开发Angular Web应用,原计划采用JWT实现身份认证,因后端暂未就绪,目前使用模拟函数实现认证逻辑:
- 编写
Auth.service.ts负责认证全流程 - 编写
Guard.service.ts拦截未登录用户对受保护路由的访问 - 会话Token存储在
localStorage中 - 在
Auth.service.ts内定义isAuthenticated状态对象,用于全局标识用户登录状态
现有代码
Auth.service.ts
@Injectable({ providedIn: 'root' }) export class AuthService { constructor(private router: Router) { this.isAuthenticated.next(false) } public isAuthenticated = new Subject<boolean>() public CheckAuthentication = () => { if (localStorage.getItem('authToken') === 'AUTH_TOKEN') { this.isAuthenticated.next(true) return true } else { this.isAuthenticated.next(false) return false } } public Login = async (username: string, password: string) => { if (username == 'admin' && password == 'admin') { localStorage.setItem('authToken', 'AUTH_TOKEN') await this.router.navigate(['/dashboard']).then(() => window.location.reload()) } } public Logout = async () => { localStorage.clear() await this.router.navigate(['/dashboard']).then(() => window.location.reload()) } }
Guard.service.ts
@Injectable({ providedIn: 'root' }) export class GuardService implements CanActivate, OnDestroy { constructor(private router: Router, private auth: AuthService){ } ngOnDestroy(): void {} canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): boolean { if(this.auth.CheckAuthentication()) { return true } else { this.router.navigate(['/secure/account/login']) return false } } }
服务调用关系
仅在app.module.ts的providers数组中声明了AuthService,两个组件调用该服务:
Login.component.ts:登录按钮点击时调用this.authService.Login(this.loginForm.value.username ?? '', this.loginForm.value.password ?? '')Navbar.component.ts:订阅isAuthenticated状态,动态渲染登录/登出按钮,完整代码如下:
@Component({ selector: 'navbar', template: ` <p-menubar [model]="this.menuItems"> <ng-template pTemplate="start"> <img src="../../assets/img/muetec.png" height="40" class="mr-2" /> </ng-template> </p-menubar> ` }) export class NavbarComponent implements OnDestroy{ menuItems: MenuItem[] private isAuthSub: Subscription constructor(public auth: AuthService) { this.isAuthSub = this.auth.isAuthenticated.subscribe(s => this.updateBar(s)) this.menuItems = [] } updateBar(s: boolean) { if (s) { this.menuItems.find(mi => mi.label == 'Account')?.items?.push( { label: 'Logout', icon: 'pi pi-lock-closed', routerLink: [ '/secure/account/logout' ] } ) } else if(!s) { this.menuItems.find(mi => mi.label == 'Account')?.items?.push( { label: 'Login', icon: 'pi pi-lock-open', routerLink: [ '/secure/account/login' ] } ) } } ngOnDestroy(): void { this.isAuthSub.unsubscribe() } }
问题现象
点击登录按钮后,页面重定向回Dashboard,之后Angular路由无内容渲染,数秒后抛出报错:
Too many calls to Location or History APIs within a short timeframe.
报错来源指向auth.service.ts和common.mjs,项目基于Angular + NX Workspaces搭建。
问题原因
- 无限重定向循环:路由配置和守卫逻辑存在冲突,大概率
/dashboard挂了路由守卫,但跳转时认证状态校验时序异常,导致反复在dashboard和登录页之间跳转,短时间触发大量历史API调用,触发浏览器限流报错。 - 强制整页刷新打断SPA生命周期:Angular是单页应用,路由导航完成后调用
window.location.reload()会重新初始化整个应用,打断Angular路由执行流程,和守卫逻辑叠加后极易触发循环跳转。 - 状态对象选型错误:使用普通
Subject存储登录态不会持久化最新值,组件订阅时如果没有新的状态推送,会直接拿不到当前登录状态,导致认证判断异常。 - 菜单更新逻辑缺陷:每次登录态变更都会直接往菜单数组追加新选项,没有清理旧项,会导致菜单重复渲染。
修复方案
- 替换状态对象为
BehaviorSubject,初始化时直接从localStorage读取登录态,删除构造函数里手动推送false的逻辑:
public isAuthenticated = new BehaviorSubject<boolean>( localStorage.getItem('authToken') === 'AUTH_TOKEN' )
- 移除登录、登出逻辑里的
window.location.reload()调用,单页应用路由跳转不需要整页刷新,同时主动推送最新登录态:
public Login = async (username: string, password: string) => { if (username == 'admin' && password == 'admin') { localStorage.setItem('authToken', 'AUTH_TOKEN') this.isAuthenticated.next(true) this.router.navigate(['/dashboard']) } } public Logout = async () => { localStorage.removeItem('authToken') this.isAuthenticated.next(false) this.router.navigate(['/dashboard']) }
- 优化路由守卫逻辑,用返回
UrlTree的方式替代手动调用navigate,避免重复导航:
canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): boolean | UrlTree { const isAuthed = this.auth.CheckAuthentication() if (isAuthed) return true return this.router.createUrlTree(['/secure/account/login']) }
- 修复导航栏菜单更新逻辑,每次更新前先清理旧的Account菜单,避免重复追加选项:
updateBar(s: boolean) { this.menuItems = this.menuItems.filter(mi => mi.label !== 'Account') const accountMenu: MenuItem = { label: 'Account', items: [] } if (s) { accountMenu.items?.push( { label: 'Logout', icon: 'pi pi-lock-closed', routerLink: ['/secure/account/logout'] } ) } else { accountMenu.items?.push( { label: 'Login', icon: 'pi pi-lock-open', routerLink: ['/secure/account/login'] } ) } this.menuItems.push(accountMenu) }
- 检查路由配置,确认
/dashboard路由的守卫逻辑正常,token存在时不会被拦截重定向到登录页,从根源上杜绝跳转死循环。
内容的提问来源于stack exchange,提问作者Oliver Karger
相关产品推荐
相关产品推荐

