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

Angular 5.2 & RxJS 5.5:HttpInterceptor拦截401重发请求遇阻

我来帮你搞定这个问题!在Angular的HTTP拦截器里处理401刷新令牌并重发请求,核心是要正确串联异步操作,确保刷新令牌的请求完成后,能带着新的请求头重新发起原请求。我给你一套可行的实现方案,再拆解里面的关键点:

完整拦截器实现代码
import { Injectable } from '@angular/core';
import {
  HttpRequest,
  HttpHandler,
  HttpInterceptor,
  HttpErrorResponse
} from '@angular/common/http';
import { Observable, throwError, BehaviorSubject } from 'rxjs';
import { catchError, switchMap, take, filter } from 'rxjs/operators';
import { AuthService } from './auth.service'; // 假设你有处理令牌的AuthService

@Injectable()
export class AuthInterceptor implements HttpInterceptor {
  // 标记是否正在刷新令牌,避免并发请求重复触发刷新
  private isRefreshing = false;
  // 用于通知等待中的请求:刷新已完成,可用新令牌重发
  private refreshTokenSubject: BehaviorSubject<any> = new BehaviorSubject<any>(null);

  constructor(private authService: AuthService) {}

  intercept(request: HttpRequest<any>, next: HttpHandler): Observable<any> {
    // 先给请求带上当前的access token
    request = this.addAuthHeader(request);

    return next.handle(request).pipe(
      catchError((error) => {
        if (error instanceof HttpErrorResponse && error.status === 401) {
          return this.handle401Error(request, next);
        } else {
          // 非401错误直接抛出
          return throwError(() => error);
        }
      })
    );
  }

  // 克隆请求并添加Authorization头
  private addAuthHeader(request: HttpRequest<any>): HttpRequest<any> {
    const accessToken = this.authService.getAccessToken();
    if (accessToken) {
      return request.clone({
        setHeaders: {
          Authorization: `Bearer ${accessToken}`
        }
      });
    }
    return request;
  }

  private handle401Error(request: HttpRequest<any>, next: HttpHandler): Observable<any> {
    if (!this.isRefreshing) {
      this.isRefreshing = true;
      this.refreshTokenSubject.next(null);

      // 先发送刷新令牌请求,拿到新token后重发原请求
      return this.authService.refreshToken().pipe(
        switchMap((newToken: any) => {
          this.isRefreshing = false;
          this.refreshTokenSubject.next(newToken.accessToken);
          // 用新token克隆原请求,发起重发
          return next.handle(this.addAuthHeader(request));
        }),
        catchError((refreshError) => {
          this.isRefreshing = false;
          // 刷新失败,直接退出登录
          this.authService.logout();
          return throwError(() => refreshError);
        })
      );
    } else {
      // 如果已有刷新请求在进行,等待刷新完成后用新token重发
      return this.refreshTokenSubject.pipe(
        filter(token => token !== null),
        take(1),
        switchMap(token => {
          return next.handle(this.addAuthHeader(request));
        })
      );
    }
  }
}
关键问题解析

为什么之前catchError返回next.handle(newReq)没触发?

因为刷新令牌的请求是异步操作,你需要用switchMap把「刷新令牌」和「重发原请求」两个Observable串联起来。如果直接在catchError里返回next.handle(...),会导致新token还没拿到就发起请求,甚至因为刷新请求还在pending,导致重发逻辑没有正确订阅执行。

核心细节说明

  1. 不可变的HttpRequest:Angular的请求对象是不可变的,必须用clone()方法修改请求头,不能直接修改原对象,否则不会生效。
  2. 并发请求处理:用isRefreshing标记和BehaviorSubject避免多个请求同时触发401时,重复调用刷新令牌接口,所有等待的请求会在刷新完成后统一用新token重发。
  3. 刷新失败兜底:如果刷新令牌也失败(比如refresh token过期),直接执行退出登录逻辑,避免无限循环请求。
配套的AuthService示例

确保你的AuthService里有对应的令牌操作方法:

// auth.service.ts
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs';

@Injectable({ providedIn: 'root' })
export class AuthService {
  private accessTokenKey = 'access_token';
  private refreshTokenKey = 'refresh_token';

  constructor(private http: HttpClient) {}

  getAccessToken(): string | null {
    return localStorage.getItem(this.accessTokenKey);
  }

  getRefreshToken(): string | null {
    return localStorage.getItem(this.refreshTokenKey);
  }

  refreshToken(): Observable<any> {
    const refreshToken = this.getRefreshToken();
    return this.http.post('/api/auth/refresh-token', { refreshToken });
  }

  logout(): void {
    localStorage.removeItem(this.accessTokenKey);
    localStorage.removeItem(this.refreshTokenKey);
    // 这里可以添加跳转到登录页的逻辑
  }
}
最后别忘了注册拦截器

在app.module.ts的providers数组中注册拦截器:

import { HTTP_INTERCEPTORS } from '@angular/common/http';
import { AuthInterceptor } from './auth.interceptor';

@NgModule({
  // ...其他配置
  providers: [
    { provide: HTTP_INTERCEPTORS, useClass: AuthInterceptor, multi: true }
  ]
})
export class AppModule { }

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.20 07:10:35