如何在Angular5拦截器中用RxJS实现HttpRequest请求队列?
我之前在Angular 5项目里踩过一模一样的坑——并发请求碰到token过期时,一堆请求同时触发401,各自去刷新token,导致服务器收到好几个刷新请求,直到其中一个成功后剩下的才能重试。核心解决思路就是统一管控token刷新状态,让所有待重试的请求共享同一个刷新结果,下面是我验证过的完整实现方案:
拦截器核心实现
首先我们需要在拦截器里维护两个关键状态:一个标记当前是否正在刷新token的布尔值,一个用来广播新token的BehaviorSubject,让所有等待的请求订阅它。
import { Injectable } from '@angular/core'; import { HttpRequest, HttpHandler, 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'; // 替换成你自己的Auth服务 @Injectable() export class AuthInterceptor implements HttpInterceptor { // 标记是否正在刷新token private isRefreshing = false; // 用来传递刷新后的新token的Subject private refreshTokenSubject: BehaviorSubject<string | null> = new BehaviorSubject<string | null>(null); constructor(private authService: AuthService) {} intercept(request: HttpRequest<any>, next: HttpHandler): Observable<any> { // 跳过刷新token的请求,避免拦截死循环 if (request.url.includes('/api/auth/refresh-token')) { return next.handle(request); } // 给请求添加Authorization头 request = this.addAuthHeader(request); return next.handle(request).pipe( catchError((error) => { // 只处理401未授权的异常 if (error instanceof HttpErrorResponse && error.status === 401) { return this.handleUnauthorized(request, next); } // 其他错误直接抛出 return throwError(error); }) ); } // 给请求添加Authorization头 private addAuthHeader(request: HttpRequest<any>): HttpRequest<any> { const accessToken = this.authService.getAccessToken(); // 从本地获取当前token if (accessToken) { return request.clone({ setHeaders: { Authorization: `Bearer ${accessToken}` } }); } return request; } // 处理401未授权逻辑 private handleUnauthorized(request: HttpRequest<any>, next: HttpHandler): Observable<any> { // 如果当前没有在刷新token,发起刷新请求 if (!this.isRefreshing) { this.isRefreshing = true; this.refreshTokenSubject.next(null); // 先发送null,标记等待状态 return this.authService.refreshToken().pipe( switchMap((newToken: string) => { this.isRefreshing = false; this.refreshTokenSubject.next(newToken); // 广播新token // 用新token重试当前请求 return next.handle(this.addAuthHeader(request)); }), catchError((error) => { this.isRefreshing = false; // 刷新失败(比如refresh token也过期),直接跳登录 this.authService.logout(); return throwError(error); }) ); } else { // 如果已经在刷新token,等待新token返回后重试请求 return this.refreshTokenSubject.pipe( filter(token => token !== null), // 过滤掉初始的null值 take(1), // 只取第一个有效token switchMap(token => { return next.handle(this.addAuthHeader(request)); }) ); } } }
AuthService配套方法
你的AuthService需要提供获取token、刷新token、保存token和登出的方法,示例如下:
import { Injectable } from '@angular/core'; import { HttpClient } from '@angular/common/http'; import { Observable } from 'rxjs'; import { map } from 'rxjs/operators'; @Injectable({ providedIn: 'root' }) export class AuthService { private readonly STORAGE_KEY_TOKEN = 'access_token'; private readonly STORAGE_KEY_REFRESH_TOKEN = 'refresh_token'; constructor(private http: HttpClient) {} // 获取当前access token getAccessToken(): string | null { return localStorage.getItem(this.STORAGE_KEY_TOKEN); } // 获取refresh token private getRefreshToken(): string | null { return localStorage.getItem(this.STORAGE_KEY_REFRESH_TOKEN); } // 刷新token接口 refreshToken(): Observable<string> { const refreshToken = this.getRefreshToken(); return this.http.post<any>('/api/auth/refresh-token', { refreshToken }).pipe( map(response => { const newAccessToken = response.access_token; // 保存新的access token到本地 localStorage.setItem(this.STORAGE_KEY_TOKEN, newAccessToken); return newAccessToken; }) ); } // 登出逻辑 logout(): void { localStorage.removeItem(this.STORAGE_KEY_TOKEN); localStorage.removeItem(this.STORAGE_KEY_REFRESH_TOKEN); // 这里可以跳转到登录页 window.location.href = '/login'; } }
关键细节说明
isRefreshing:这个布尔值是核心锁,确保同一时间只有一个刷新token的请求发送到服务器,避免重复刷新。refreshTokenSubject:作为所有等待请求的消息通道,当刷新成功后,新token会被广播给所有订阅它的请求,这些请求会立即用新token重试。- 跳过刷新token的请求:必须排除刷新token本身的请求,否则会进入拦截死循环。
最后一步:注册拦截器
别忘了在你的AppModule里注册这个拦截器,让Angular生效:
import { NgModule } from '@angular/core'; import { HTTP_INTERCEPTORS } from '@angular/common/http'; import { AuthInterceptor } from './auth.interceptor'; @NgModule({ ... providers: [ { provide: HTTP_INTERCEPTORS, useClass: AuthInterceptor, multi: true // 允许多个拦截器 } ] }) export class AppModule {}
这个方案能完美解决你遇到的问题:当多个并发请求触发401时,只有第一个请求会发起token刷新,其他请求都会进入等待状态,直到新token返回后,所有等待的请求都会用新token重试,不会再出现多个未授权请求和重复刷新的情况。
内容的提问来源于stack exchange,提问作者resu
相关产品推荐
相关产品推荐

