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

Angular拦截器捕获Token过期,实现自动刷新并重发请求问询

我来帮你搞定这个token过期自动刷新并重发请求的需求,这是前后端分离项目里超常见的场景,咱们一步步来调整你的拦截器代码:

调整后的Angular拦截器实现

我们需要在拦截器里完成「捕获token过期错误→刷新token→重发原请求」的完整逻辑,同时还要避免多个请求同时触发重复刷新的问题,下面是完整的实现代码:

import { Injectable } from '@angular/core';
import { HttpRequest, HttpHandler, HttpEvent, HttpInterceptor, HttpErrorResponse } from '@angular/common/http';
import { Observable, BehaviorSubject, throwError } from 'rxjs';
import { catchError, filter, take, switchMap } from 'rxjs/operators';
import { AuthService } from './auth.service'; // 假设你有专门处理token的AuthService

@Injectable()
export class AuthInterceptor implements HttpInterceptor {
  // 标记是否正在刷新token,避免并发请求重复触发刷新
  private isRefreshing = false;
  // 用BehaviorSubject存储等待重发的请求队列
  private refreshTokenSubject: BehaviorSubject<any> = new BehaviorSubject<any>(null);

  constructor(private authService: AuthService) {}

  intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
    // 先给需要授权的请求添加Authorization头
    const token = this.authService.getToken();
    if (token) {
      request = this.addTokenToRequest(request, token);
    }

    return next.handle(request).pipe(
      catchError((error: HttpErrorResponse) => {
        // 判断是否是Laravel返回的token过期错误
        if (error.status === 401 && error.error?.message === 'token_expired') {
          return this.handleTokenRefresh(request, next);
        }
        // 其他错误直接抛出,交给业务组件处理
        return throwError(() => error);
      })
    );
  }

  // 给请求添加Authorization头的工具方法
  private addTokenToRequest(request: HttpRequest<any>, token: string): HttpRequest<any> {
    return request.clone({
      setHeaders: {
        Authorization: `Bearer ${token}`
      }
    });
  }

  // 处理token刷新和请求重发的核心逻辑
  private handleTokenRefresh(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
    if (!this.isRefreshing) {
      this.isRefreshing = true;
      this.refreshTokenSubject.next(null);

      // 调用AuthService的刷新token方法
      return this.authService.refreshToken().pipe(
        switchMap((newToken: string) => {
          this.isRefreshing = false;
          this.refreshTokenSubject.next(newToken);
          // 更新本地存储的token
          this.authService.saveToken(newToken);
          // 用新token重新发送原来的请求
          return next.handle(this.addTokenToRequest(request, newToken));
        }),
        catchError((err) => {
          this.isRefreshing = false;
          // 如果刷新token失败(比如refresh token也过期了),直接登出跳登录页
          this.authService.logout();
          return throwError(() => err);
        })
      );
    } else {
      // 如果已经在刷新token,就等待刷新完成后再重发当前请求
      return this.refreshTokenSubject.pipe(
        filter(token => token !== null),
        take(1),
        switchMap(token => {
          return next.handle(this.addTokenToRequest(request, token));
        })
      );
    }
  }
}
配套的AuthService示例

你需要一个专门的AuthService来处理token的存储、获取和刷新请求,示例代码如下:

import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable, map } from 'rxjs';
import { Router } from '@angular/router';

@Injectable({
  providedIn: 'root'
})
export class AuthService {
  private readonly tokenKey = 'auth_token';
  private readonly refreshTokenKey = 'refresh_token';

  constructor(private http: HttpClient, private router: Router) {}

  // 从本地存储获取当前token
  getToken(): string | null {
    return localStorage.getItem(this.tokenKey);
  }

  // 保存新token到本地存储
  saveToken(token: string): void {
    localStorage.setItem(this.tokenKey, token);
  }

  // 获取refresh token
  getRefreshToken(): string | null {
    return localStorage.getItem(this.refreshTokenKey);
  }

  // 调用Laravel的刷新token接口
  refreshToken(): Observable<string> {
    const refreshToken = this.getRefreshToken();
    return this.http.post<any>('/api/refresh-token', { refresh_token: refreshToken })
      .pipe(
        map(response => {
          // 假设Laravel返回包含新access_token和refresh_token的响应
          this.saveToken(response.access_token);
          localStorage.setItem(this.refreshTokenKey, response.refresh_token);
          return response.access_token;
        })
      );
  }

  // 登出操作:清理token并跳转登录页
  logout(): void {
    localStorage.removeItem(this.tokenKey);
    localStorage.removeItem(this.refreshTokenKey);
    this.router.navigate(['/login']);
  }
}
关键细节提醒
  • 排除无需授权的请求:如果登录、刷新token这类请求不需要加Authorization头,记得在拦截器开头排除,避免无限循环:
    if (request.url.includes('/api/login') || request.url.includes('/api/refresh-token')) {
      return next.handle(request);
    }
    
  • 适配后端错误格式:根据你Laravel实际返回的错误结构调整判断逻辑,比如有些后端会把错误信息放在error.error.message里,需要对应修改。
  • 并发请求处理:代码里的isRefreshing和refreshTokenSubject已经处理了并发场景,多个请求同时因token过期失败时,只会触发一次刷新,所有请求都会用新token重发。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.20 11:15:20