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

Angular 5 HttpInterceptor请求前刷新Token失效问题排查与修复

Fixing the Refresh Token Request Issue in Angular 5 HttpInterceptor

Let's break down the problem and fix your code step by step. The core issue here is related to how the Observable from refreshToken() is being handled, plus some minor state management glitches that are blocking subsequent requests.

Key Issues in Your Current Code

  1. Duplicate isRefreshingToken Reset: You're setting this.isRefreshingToken = false both inside the switchMap success handler and in the finally block. This can lead to race conditions where the flag is reset prematurely, causing unexpected behavior with waiting requests.
  2. Incomplete Error Propagation: When the refresh token request fails, waiting requests aren't properly notified of the error, leaving them hanging indefinitely instead of failing gracefully.
  3. Outdated RxJS Chaining: While Angular 5 uses RxJS 5.x, sticking to pipeable operators makes the code clearer and aligns with modern practices, which also helps avoid subtle Observable subscription issues.

Modified Interceptor Code

Here's the corrected version with detailed explanations:

@Injectable()
export class TokenInterceptor implements HttpInterceptor {
  private refreshTokenSubject = new BehaviorSubject<string | null>(null);
  private isRefreshingToken = false;

  constructor(private authService: AuthService) { }

  intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
    const tokenData = AuthService.getCurrentSessionData();

    // If token is already being refreshed, wait for the new token to retry the request
    if (this.isRefreshingToken) {
      return this.refreshTokenSubject.pipe(
        filter(token => token !== null),
        take(1),
        switchMap(token => next.handle(this.setAuthHeader(request, token))),
        catchError(err => throwError(() => err))
      );
    }

    // Skip token checks if no access token exists
    const tokenSet = tokenData?.token;
    if (!tokenSet) {
      return next.handle(request);
    }

    // Proceed with request if token hasn't expired
    const tokenExpired = new Date(tokenData.expirationDate) < new Date();
    if (!tokenExpired) {
      return next.handle(this.setAuthHeader(request, tokenData.token));
    }

    // Logout immediately if refresh token is also expired
    const tokenRefreshable = tokenData.refreshToken && new Date(tokenData.refreshTokenExpirationDate) > new Date();
    if (!tokenRefreshable) {
      this.authService.logout();
      return throwError(() => 'Refresh token expired');
    }

    // Start token refresh process
    this.isRefreshingToken = true;
    this.refreshTokenSubject.next(null); // Signal waiting requests to hold on

    return this.authService.refreshToken().pipe(
      switchMap((res: any) => {
        // Update session data with new tokens
        AuthService.storeSessionData(res, Utils.getLocalStorageItem(STORAGE_KEYS.STAY_LOGGED_IN));
        this.isRefreshingToken = false;
        this.refreshTokenSubject.next(res.access_token); // Send new token to waiting requests
        // Retry the original request with fresh token
        return next.handle(this.setAuthHeader(request, res.access_token));
      }),
      catchError((err) => {
        // Handle refresh failure: reset state, logout, and notify waiting requests
        this.isRefreshingToken = false;
        this.authService.logout();
        this.refreshTokenSubject.error(err); // Propagate error to waiting requests
        this.refreshTokenSubject.next(null); // Reset subject for future requests
        return throwError(() => err);
      })
    );
  }

  private setAuthHeader(request: HttpRequest<any>, token: string): HttpRequest<any> {
    return request.clone({
      setHeaders: {
        Authorization: `Bearer ${token}`,
      },
    });
  }
}

What Changed & Why

  1. Cleaned Up State Management: Removed the redundant finally block to avoid resetting isRefreshingToken twice. Now we only reset the flag once in both success and error handlers, ensuring consistent state.
  2. Proper Error Notification: When the refresh token request fails, we call refreshTokenSubject.error(err) to notify all waiting requests of the failure, so they don't hang and can handle the error appropriately.
  3. Pipeable Operators: Switched to using pipe() for RxJS operators (like filter, take, switchMap) which is more readable and aligns with Angular's recommended practices for RxJS usage.
  4. Meaningful Error Throwing: Replaced Observable.throw('') with throwError(() => err) to propagate actual error details, making debugging easier and allowing components to handle specific error cases.

Verify the refreshToken Method

Your existing refreshToken method is correct, but double-check that environment.apiPath is properly configured and your backend accepts the refresh_token payload. Here's the method again for reference:

public refreshToken() {
  const tokenData = AuthService.getCurrentSessionData();
  return this.http.post(
    `${environment.apiPath}/auth/refresh`,
    { refresh_token: tokenData.refreshToken },
  );
}

With these changes, the refreshToken() request will trigger correctly, and subsequent requests will either retry with the new token if refresh succeeds, or fail immediately (and trigger logout) if refresh fails.

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.29 07:49:45