You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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搭建。

问题原因

  1. 无限重定向循环:路由配置和守卫逻辑存在冲突,大概率/dashboard挂了路由守卫,但跳转时认证状态校验时序异常,导致反复在dashboard和登录页之间跳转,短时间触发大量历史API调用,触发浏览器限流报错。
  2. 强制整页刷新打断SPA生命周期:Angular是单页应用,路由导航完成后调用window.location.reload()会重新初始化整个应用,打断Angular路由执行流程,和守卫逻辑叠加后极易触发循环跳转。
  3. 状态对象选型错误:使用普通Subject存储登录态不会持久化最新值,组件订阅时如果没有新的状态推送,会直接拿不到当前登录状态,导致认证判断异常。
  4. 菜单更新逻辑缺陷:每次登录态变更都会直接往菜单数组追加新选项,没有清理旧项,会导致菜单重复渲染。

修复方案

  1. 替换状态对象为BehaviorSubject,初始化时直接从localStorage读取登录态,删除构造函数里手动推送false的逻辑:
public isAuthenticated = new BehaviorSubject<boolean>(
  localStorage.getItem('authToken') === 'AUTH_TOKEN'
)
  1. 移除登录、登出逻辑里的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'])
}
  1. 优化路由守卫逻辑,用返回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'])
}
  1. 修复导航栏菜单更新逻辑,每次更新前先清理旧的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)
}
  1. 检查路由配置,确认/dashboard路由的守卫逻辑正常,token存在时不会被拦截重定向到登录页,从根源上杜绝跳转死循环。

内容的提问来源于stack exchange,提问作者Oliver Karger

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.27 13:48:23