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

优化Angular HTTP拦截器:移除刷新令牌请求检查避免无限循环

无需显式检查刷新令牌端点,解决Angular拦截器无限循环问题

要去掉硬编码的isRefreshTokenRequest检查同时避免无限循环,核心思路是让拦截器能识别出“刷新令牌请求”并跳过认证逻辑,推荐用Angular HttpContext自定义标记的方案——这种方式不依赖端点URL,更灵活且不会把标记暴露给服务器。

步骤1:给刷新请求添加跳过拦截器的标记

在你的AuthService里,定义一个全局的HttpContext标记,然后在refreshToken方法创建请求时带上这个标记:

// auth.service.ts
import { HttpClient, HttpContext, HttpRequest } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { Observable } from 'rxjs';
import { AuthResponse } from '../interfaces/auth-response';

@Injectable({ providedIn: 'root' })
export class AuthService {
  constructor(private httpClient: HttpClient) {}

  // 定义自定义上下文标记,默认值为false
  static readonly SKIP_AUTH_INTERCEPTOR = new HttpContextToken<boolean>(() => false);

  refreshToken(): Observable<AuthResponse> {
    const refreshToken = this.getRefreshToken(); // 替换成你实际获取refresh token的逻辑
    const request = new HttpRequest(
      'POST',
      '/api/authentication/refresh-token',
      { refreshToken },
      {
        // 给该请求添加跳过拦截器的标记
        context: new HttpContext().set(AuthService.SKIP_AUTH_INTERCEPTOR, true)
      }
    );
    return this.httpClient.request<AuthResponse>(request);
  }

  // 其他已有的方法:accessToken(), getRefreshToken(), logOut()...
}

步骤2:修改拦截器,通过标记判断是否跳过处理

移除原来的isRefreshTokenRequest函数,替换为检查自定义标记的逻辑:

// auth.interceptor.ts
import { inject } from '@angular/core';
import { HttpErrorResponse, HttpEvent, HttpHandlerFn, HttpRequest } from '@angular/common/http';
import { BehaviorSubject, catchError, filter, Observable, switchMap, take, throwError } from 'rxjs';
import { AuthService } from '../services/auth/auth.service';
import { AuthResponse } from '../interfaces/auth-response';

let isRefreshing = false;
const refreshTokenSubject: BehaviorSubject<string | null> = new BehaviorSubject<string | null>(null);

export function authInterceptor(request: HttpRequest<unknown>, next: HttpHandlerFn): Observable<HttpEvent<unknown>> {
  const authService = inject(AuthService);

  // 检查请求是否带有跳过拦截器的标记,是则直接放行
  if (request.context.get(AuthService.SKIP_AUTH_INTERCEPTOR)) {
    return next(request);
  }

  // 给普通请求添加access token
  if (authService.accessToken()) {
    request = addToken(request, authService.accessToken()!);
  }

  function handle401Error(request: HttpRequest<unknown>, next: HttpHandlerFn): Observable<HttpEvent<unknown>> {
    if (!isRefreshing) {
      isRefreshing = true;
      refreshTokenSubject.next(null);

      return authService.refreshToken().pipe(
        switchMap((response: AuthResponse) => {
          isRefreshing = false;
          refreshTokenSubject.next(response.accessToken);
          return next(addToken(request, response.accessToken));
        }),
        catchError((error) => {
          isRefreshing = false;
          if (
            error instanceof HttpErrorResponse &&
            error.status === 401 &&
            (error.error?.message === 'Token expired' || error.error?.message === 'Invalid token')
          ) {
            authService.logOut();
          }
          return throwError(() => error);
        }),
      );
    }
    return refreshTokenSubject.pipe(
      filter((token) => token !== null),
      take(1),
      switchMap((token) => next(addToken(request, token!))),
    );
  }

  return next(request).pipe(
    catchError((error: HttpErrorResponse) => {
      if (error.status === 401) {
        return handle401Error(request, next);
      }
      return throwError(() => error);
    }),
  );
}

function addToken(request: HttpRequest<unknown>, token: string) {
  return request.clone({
    setHeaders: {
      Authorization: `Bearer ${token}`,
    },
  });
}

为什么能避免无限循环?

  1. 标记识别:刷新令牌请求带有SKIP_AUTH_INTERCEPTOR标记,拦截器会直接放行,不会给它添加access token,也不会进入401错误处理逻辑。
  2. 状态锁控制:全局的isRefreshing状态确保同一时间只有一个刷新请求在执行,就算出现意外情况,也不会重复触发刷新流程。

内容的提问来源于stack exchange,提问作者Muhammad-Ali

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.20 21:14:57