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

如何在路由文件中处理已登录用户从首页到私有路由的重定向?

问题描述

我有如下路由文件:

{
    path: '',
    component: LayoutComponent,
    children: [
      {
        path: '',
        component: HomeComponent,
      },
      {
        path: 'login',
        loadComponent: () =>
          import('./features/login/login.component').then((m) => m.LoginComponent),
      },
      {
        path: 'horse-catalogue',
        canMatch: [isLoggedIn],
        loadComponent: () =>
          import('./features/horse-catalogue/horse-catalogue.component').then(
            (m) => m.HorseCatalogueComponent
          ),
        resolve: { data: horseCatalogueResolver },
      },
   ...
}

export const isLoggedIn: CanMatchFn = () => {
  const authService = inject(AuthenticationService);
  return authService.isLoggedIn().pipe(map((user) => !!user));
};

authService.isLoggedIn()会发起网络请求判断用户是否登录(因登录用户会获得HttpOnly、Secure Cookie,前端无法读取,我希望遵循所有验证在服务端完成的模式)。

我最初考虑给HomeComponent添加另一个守卫,实现类似逻辑(仅判断登录状态的反向),但觉得给未登录用户每次都调用验证接口有些冗余。此外我有一个拦截器,会捕获所有接口的401错误并显示“未授权”通知,我不希望用户刚进入应用就看到该提示。

请问如何通过守卫声明式处理这一需求?


解决方案

1. 给认证服务添加缓存,避免重复请求

给AuthenticationService添加请求缓存逻辑,确保短时间内isLoggedIn()只发起一次请求,后续直接复用缓存结果,解决重复调用接口的冗余问题:

@Injectable({ providedIn: 'root' })
export class AuthenticationService {
  private loginStatusCache: Observable<boolean> | null = null;
  private cacheExpiry = 5000; // 缓存5秒,可根据业务调整

  isLoggedIn(): Observable<boolean> {
    if (!this.loginStatusCache) {
      this.loginStatusCache = this.http.get<{ isLoggedIn: boolean }>('/api/auth/check')
        .pipe(
          map(res => res.isLoggedIn),
          shareReplay({ refCount: true, bufferSize: 1 }),
          // 缓存到期后自动清空
          delay(this.cacheExpiry),
          tap(() => this.loginStatusCache = null),
          catchError(() => {
            this.loginStatusCache = null;
            return of(false);
          })
        );
    }
    return this.loginStatusCache;
  }
}

2. 创建反向守卫并调整拦截器规则

实现反向守卫isNotLoggedIn

用于限制已登录用户访问登录页,同时处理路由重定向:

export const isNotLoggedIn: CanMatchFn = () => {
  const authService = inject(AuthenticationService);
  const router = inject(Router);
  
  return authService.isLoggedIn().pipe(
    map(isLoggedIn => !isLoggedIn),
    tap(isNotLoggedIn => {
      if (!isNotLoggedIn) {
        router.navigate(['/']);
      }
    })
  );
};

修改拦截器排除验证接口

在拦截器中跳过登录状态检查接口,避免用户刚进入应用就触发401提示:

@Injectable()
export class AuthInterceptor implements HttpInterceptor {
  intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
    // 排除登录状态验证接口,不触发401通知
    if (req.url.includes('/api/auth/check')) {
      return next.handle(req);
    }

    return next.handle(req).pipe(
      catchError((error: HttpErrorResponse) => {
        if (error.status === 401) {
          inject(NotificationService).showError('未授权');
        }
        return throwError(() => error);
      })
    );
  }
}

3. 配置路由守卫

调整路由规则,给首页和登录页添加对应守卫,实现声明式的状态控制:

{
    path: '',
    component: LayoutComponent,
    children: [
      {
        path: '',
        component: HomeComponent,
        canMatch: [
          () => {
            const authService = inject(AuthenticationService);
            const router = inject(Router);
            return authService.isLoggedIn().pipe(
              map(isLoggedIn => {
                if (isLoggedIn) {
                  router.navigate(['/horse-catalogue']);
                  return false; // 已登录则不匹配首页路由
                }
                return true; // 未登录正常显示首页
              })
            );
          }
        ]
      },
      {
        path: 'login',
        canMatch: [isNotLoggedIn],
        loadComponent: () =>
          import('./features/login/login.component').then((m) => m.LoginComponent),
      },
      {
        path: 'horse-catalogue',
        canMatch: [isLoggedIn],
        loadComponent: () =>
          import('./features/horse-catalogue/horse-catalogue.component').then(
            (m) => m.HorseCatalogueComponent
          ),
        resolve: { data: horseCatalogueResolver },
      },
   ...
}

4. 补充说明

缓存机制会在页面刷新时重新发起验证请求,但因为拦截器已排除该接口,不会弹出401提示;同时缓存有效期内的路由切换不会重复调用接口,兼顾了安全性和性能。


内容的提问来源于stack exchange,提问作者James Barrett

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.22 14:20:02