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

Angular 13中Token过期时自动登出的实现问题及代码修正

问题分析与修正方案

你的拦截器代码存在几个关键问题,导致Token过期时无法自动登出,以下是具体问题和修复方案:

原代码的核心问题

  1. 错误处理方式不对:tap的错误回调仅作为副作用执行,不会正确捕获HTTP错误流,部分场景下错误会被其他地方拦截导致登出逻辑不触发。
  2. 未区分错误类型:直接取error.status可能不准确,Angular的HTTP错误是HttpErrorResponse实例,需要先判断类型避免非HTTP错误触发逻辑。
  3. 未排除免鉴权请求:如果登录、注册这类接口返回401,会触发登出导致循环跳转。
  4. 仅依赖后端返回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(() => {});
  }
}

关键修复点说明

  1. 用catchError替代tap处理错误:catchError能正确捕获HTTP错误流,确保登出逻辑执行,同时不中断后续错误处理。
  2. 排除免鉴权请求:避免登录接口返回401时触发登出,导致循环跳转。
  3. 前端主动检测Token过期:页面加载和每次判断登录状态时,都检查Token是否过期,不用等用户发起请求。
  4. 完善登出逻辑:支持异步操作,确保清除Token后再跳转到登录页。

内容的提问来源于stack exchange,提问作者Khalil Fathalli

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.05 23:35:18