如何在路由文件中处理已登录用户从首页到私有路由的重定向?
问题描述
我有如下路由文件:
{ 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
相关产品推荐
相关产品推荐

