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

Angular 9:解决CanDeactivate守卫与会话超时的冲突问题

解决会话过期时CanDeactivate守卫弹窗阻塞问题

我完全理解你的痛点——会话悄悄过期了,用户想离开页面时却先被未保存修改的弹窗拦住,点击确认后才会触发401跳转,体验确实糟糕。咱们来把这个逻辑理顺,让守卫先校验会话状态,再决定是弹提示还是直接跳登出。

核心思路

利用RxJS的异步流特性,在守卫的canDeactivate方法里先串联会话校验逻辑:

  1. 先调用后端接口确认会话有效性
  2. 如果会话失效,直接触发登出流程,跳过弹窗
  3. 如果会话有效,再执行原有的脏检查和弹窗逻辑

步骤1:改造AuthenticationService的会话校验方法

把原来的Promise改为Observable,方便和守卫里的RxJS操作符结合:

// authentication.service.ts
isAuthenticated(): Observable<boolean> {
  const serverUrl = this.baseURL + 'isValidSession';
  return this.http.get(serverUrl, httpOptions).pipe(
    // 会话有效时返回true
    map(() => true),
    catchError((error) => {
      if (error.status === 401) {
        // 会话失效返回false
        return of(false);
      }
      // 其他错误场景默认认为会话有效(可根据业务调整)
      return of(true);
    })
  );
}

步骤2:修改DirtyCheckGuard逻辑

在守卫里先执行会话校验,再分支处理:

// dirty-check.guard.ts
canDeactivate(component: DirtyComponent, currentRoute: ActivatedRouteSnapshot): Observable<boolean> | boolean {
  let formIsDirty = false;
  // ... 保留你原有设置formIsDirty的逻辑 ...

  // 先校验会话状态,再处理脏检查
  return this.authenticationService.isAuthenticated().pipe(
    switchMap((isSessionValid) => {
      if (!isSessionValid) {
        // 会话失效:直接触发登出,返回false阻止当前导航
        this.authenticationService.logout(this.router.url);
        return of(false);
      }

      // 会话有效:执行原有的脏检查逻辑
      return component.isDirty$.pipe(
        switchMap((dirty) => {
          if ((dirty === false && !formIsDirty)) {
            this.checkPage(currentRoute.data.origin);
            return of(true);
          } else if (dirty === true || (dirty === false && formIsDirty)) {
            const content = component.isPrinting ? this.contentPrint : this.contentNormal;
            const confirmText = component.isPrinting ? 'Drucken' : 'Verlassen';
            
            return this.modalService.create({
              nzTitle: 'Not saved',
              nzContent: content,
              nzFooter: [
                {
                  label: 'Abbrechen',
                  onClick: () => this.modalService.closeAll(false)
                },
                {
                  label: confirmText,
                  type: 'primary',
                  onClick: () => {
                    this.checkPage(currentRoute.data.origin);
                    this.modalService.closeAll(true);
                  }
                }
              ]
            }).afterClose.pipe(
              // 根据弹窗关闭结果返回是否允许导航
              map((result) => result === true)
            );
          }
          return of(true);
        }),
        take(1)
      );
    })
  );
}

步骤3:优化性能(可选)

为了避免每次守卫触发都调用后端接口,可以在AuthenticationService里缓存会话状态:

// authentication.service.ts
private sessionValid$ = new BehaviorSubject<boolean>(true);

isAuthenticated(): Observable<boolean> {
  const serverUrl = this.baseURL + 'isValidSession';
  return this.http.get(serverUrl, httpOptions).pipe(
    tap(() => this.sessionValid$.next(true)),
    map(() => true),
    catchError((error) => {
      if (error.status === 401) {
        this.sessionValid$.next(false);
        return of(false);
      }
      this.sessionValid$.next(true);
      return of(true);
    })
  );
}

// 提供缓存的会话状态
getSessionValid$(): Observable<boolean> {
  return this.sessionValid$.asObservable();
}

同时在HttpInterceptor里收到401时更新缓存状态:

// interceptor.ts
if (error.status === 401 ) {
  this.authenticationService.sessionValid$.next(false);
  this.authenticationService.logout(requestRoute);
}

这样守卫可以先检查缓存状态,只有缓存有效时才去调用后端接口,减少不必要的请求。

关键注意点

  • 确保isValidSession接口在会话过期时会返回401,符合BasicAuth的认证逻辑
  • 模态框的afterClose要正确传递导航结果(true/false),让Angular能正确判断是否允许页面跳转
  • 如果你的NzModal版本不同,afterClose的返回值逻辑可能需要微调,以匹配实际的按钮点击结果

内容的提问来源于stack exchange,提问作者D. K.

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.07 20:22:53