MST
星途 面试题库

面试题:Angular HTTP请求拦截器在处理错误响应时的应用

假设在使用Angular的HTTP请求拦截器时,后端返回401(未授权)错误,要求在拦截器中捕获该错误,并自动触发一个刷新令牌(refresh token)的请求,刷新成功后重新发起原请求,简述实现思路并给出关键代码。
49.9万 热度难度
前端开发Angular

知识考点

AI 面试

面试题答案

一键面试

实现思路

  1. 创建一个HTTP请求拦截器,在intercept方法中捕获401错误。
  2. 当捕获到401错误时,触发刷新令牌请求。
  3. 刷新令牌成功后,将新的令牌存储起来,并重新发起原请求。
  4. 为避免多次刷新令牌导致的竞态条件,使用一个标志位和一个队列来管理请求。

关键代码

import { Injectable } from '@angular/core';
import {
  HttpEvent,
  HttpHandler,
  HttpInterceptor,
  HttpRequest,
  HttpResponse
} from '@angular/common/http';
import { Observable, catchError, finalize, switchMap } from 'rxjs';
import { TokenService } from './token.service';

@Injectable()
export class AuthInterceptor implements HttpInterceptor {
  private isRefreshing = false;
  private refreshTokenSubject: any = new Observable();

  constructor(private tokenService: TokenService) {}

  intercept(
    request: HttpRequest<any>,
    next: HttpHandler
  ): Observable<HttpEvent<any>> {
    request = this.addToken(request);
    return next.handle(request).pipe(
      catchError((error) => {
        if (error.status === 401) {
          return this.handle401Error(request, next);
        }
        return Observable.throwError(error);
      })
    );
  }

  private addToken(request: HttpRequest<any>): HttpRequest<any> {
    const token = this.tokenService.getToken();
    if (token) {
      return request.clone({
        setHeaders: {
          Authorization: `Bearer ${token}`
        }
      });
    }
    return request;
  }

  private handle401Error(
    request: HttpRequest<any>,
    next: HttpHandler
  ): Observable<HttpEvent<any>> {
    if (!this.isRefreshing) {
      this.isRefreshing = true;
      this.refreshTokenSubject = this.tokenService.refreshToken().pipe(
        switchMap((newToken: string) => {
          this.tokenService.setToken(newToken);
          this.isRefreshing = false;
          return next.handle(this.addToken(request));
        }),
        catchError((refreshError) => {
          this.isRefreshing = false;
          // 处理刷新令牌失败,例如重定向到登录页
          return Observable.throwError(refreshError);
        }),
        finalize(() => {
          this.refreshTokenSubject = new Observable();
        })
      );
    }
    return this.refreshTokenSubject.pipe(
      switchMap(() => {
        return next.handle(this.addToken(request));
      })
    );
  }
}

假设TokenService是一个服务,用于管理令牌,示例代码如下:

import { Injectable } from '@angular/core';

@Injectable()
export class TokenService {
  private tokenKey = 'auth_token';

  getToken(): string | null {
    return localStorage.getItem(this.tokenKey);
  }

  setToken(token: string): void {
    localStorage.setItem(this.tokenKey, token);
  }

  refreshToken(): Observable<string> {
    // 实际实现中,这里应发起刷新令牌的HTTP请求
    // 这里简单返回一个模拟的新令牌
    return new Observable((observer) => {
      const newToken = 'new_refreshed_token';
      observer.next(newToken);
      observer.complete();
    });
  }
}

app.module.ts中注册拦截器:

import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform - browser';
import { HttpClientModule, HTTP_INTERCEPTORS } from '@angular/common/http';
import { AppComponent } from './app.component';
import { AuthInterceptor } from './auth.interceptor';
import { TokenService } from './token.service';

@NgModule({
  declarations: [AppComponent],
  imports: [BrowserModule, HttpClientModule],
  providers: [
    TokenService,
    {
      provide: HTTP_INTERCEPTORS,
      useClass: AuthInterceptor,
      multi: true
    }
  ],
  bootstrap: [AppComponent]
})
export class AppModule {}