Angular 13中Token过期时自动登出的实现问题及代码修正
问题分析与修正方案
你的拦截器代码存在几个关键问题,导致Token过期时无法自动登出,以下是具体问题和修复方案:
原代码的核心问题
- 错误处理方式不对:
tap的错误回调仅作为副作用执行,不会正确捕获HTTP错误流,部分场景下错误会被其他地方拦截导致登出逻辑不触发。 - 未区分错误类型:直接取
error.status可能不准确,Angular的HTTP错误是HttpErrorResponse实例,需要先判断类型避免非HTTP错误触发逻辑。 - 未排除免鉴权请求:如果登录、注册这类接口返回401,会触发登出导致循环跳转。
- 仅依赖后端返回401:用户在Token过期后未发起请求时,前端不会主动检测并登出,体验不佳。
修正后的拦截器代码
import { Injectable } from '@angular/core'; import { HttpRequest, HttpHandler, HttpInterceptor, HttpErrorResponse } from '@angular/common/http'; import { Observable, throwError } from 'rxjs'; import { catchError } from 'rxjs/operators'; import { UserService } from './user.service'; import { Router } from '@angular/router'; @Injectable() export class AuthInterceptor implements HttpInterceptor { // 配置无需鉴权的接口路径,根据实际业务调整 private excludedPaths = ['/api/auth/login', '/api/auth/register']; constructor( private userService: UserService, private router: Router ) { } intercept(request: HttpRequest<any>, next: HttpHandler): Observable<any> { // 仅给需要鉴权的请求添加Token if (this.userService.isAuthenticated() && !this.isExcludedPath(request.url)) { request = request.clone({ setHeaders: { Authorization: `Bearer ${this.userService.getToken()}` } }); } return next.handle(request).pipe( catchError((error: HttpErrorResponse) => { // 仅处理HTTP返回的401/403错误 if (error.status === 401 || error.status === 403) { this.userService.logout().then(() => { this.router.navigate(['/login']); }); } // 抛出错误供后续业务逻辑处理 return throwError(() => error); }) ); } // 判断当前请求是否属于免鉴权路径 private isExcludedPath(url: string): boolean { return this.excludedPaths.some(path => url.includes(path)); } }
优化UserService,增加前端主动过期检测
仅依赖后端401不够,前端可以主动检测Token是否过期,提升用户体验:
import { Injectable } from '@angular/core'; @Injectable({ providedIn: 'root' }) export class UserService { private tokenKey = 'auth_token'; constructor() { // 页面加载时自动检查Token是否过期 this.checkTokenExpiration(); } getToken(): string | null { return localStorage.getItem(this.tokenKey); } setToken(token: string): void { localStorage.setItem(this.tokenKey, token); } isAuthenticated(): boolean { const token = this.getToken(); return !!token && !this.isTokenExpired(token); } // 手动解析Token判断是否过期(无需第三方库) private isTokenExpired(token: string): boolean { try { const payload = JSON.parse(atob(token.split('.')[1])); const expirationTime = payload.exp * 1000; return expirationTime < Date.now(); } catch { return true; } } // 主动检查Token过期,过期则自动登出 checkTokenExpiration(): void { if (this.getToken() && this.isTokenExpired(this.getToken()!)) { this.logout(); } } // 完善登出逻辑,支持异步操作(比如清除缓存、调用后端登出接口) async logout(): Promise<void> { localStorage.removeItem(this.tokenKey); // 可选:调用后端登出接口 // await this.http.post('/api/auth/logout', {}).toPromise().catch(() => {}); } }
关键修复点说明
- 用
catchError替代tap处理错误:catchError能正确捕获HTTP错误流,确保登出逻辑执行,同时不中断后续错误处理。 - 排除免鉴权请求:避免登录接口返回401时触发登出,导致循环跳转。
- 前端主动检测Token过期:页面加载和每次判断登录状态时,都检查Token是否过期,不用等用户发起请求。
- 完善登出逻辑:支持异步操作,确保清除Token后再跳转到登录页。
内容的提问来源于stack exchange,提问作者Khalil Fathalli
相关产品推荐
相关产品推荐

