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

如何避免多个API请求因访问令牌过期返回401时重复调用刷新令牌接口

如何避免多个API请求因访问令牌过期返回401时重复调用刷新令牌接口

我完全理解你的困扰——当多个API请求同时因为access token过期返回401时,每个请求都会单独触发一次refresh token接口调用,最后只有一次成功,其他全是冗余请求,既浪费资源又不够高效。咱们可以通过让刷新令牌请求成为共享的单例Observable来解决这个问题,确保同一时间只会发起一次refresh请求,所有触发401的请求都复用这次请求的结果。

核心思路

问题根源在于当前的错误拦截器里,每个401请求都会独立调用refresh()方法发起新的HTTP请求。我们需要在AuthService里做一层封装:当有第一个请求触发刷新时,保存这个请求的Observable,后续的401请求直接订阅这个已有的Observable,直到刷新请求完成(成功或失败)后再重置状态。

具体代码修改

第一步:改造AuthService,实现共享的刷新请求

@Injectable({ providedIn: 'root' })
export class AuthService {
  // 用来缓存当前正在进行的refresh请求,避免重复调用
  private refreshTokenRequest: Observable<any> | null = null;

  constructor(private http: HttpClient, private storage: StorageService) {}

  refresh(): Observable<any> {
    // 如果已经有正在进行的refresh请求,直接返回这个请求的Observable
    if (this.refreshTokenRequest) {
      return this.refreshTokenRequest;
    }

    // 没有正在进行的请求,发起新的refresh请求
    const storedTokens = this.getTokens();
    this.refreshTokenRequest = this.http.post('/auth/refresh', {}, {
      headers: new HttpHeaders().set('Authorization', storedTokens.refresh_token)
    }).pipe(
      tap((newTokens) => {
        // 保存新获取的tokens到存储
        this.saveTokens(newTokens);
      }),
      finalize(() => {
        // 请求完成(成功/失败)后,清空缓存的请求引用
        this.refreshTokenRequest = null;
      })
    );

    return this.refreshTokenRequest;
  }

  // 你的其他已有方法:getTokens、saveTokens、logout等
}

第二步:修改ErrorInterceptor(逻辑基本不变,复用改造后的refresh方法)

@Injectable()
export class ErrorInterceptor implements HttpInterceptor {
  constructor(private router: Router, private authService: AuthService) { }

  intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
    return next.handle(request).pipe(
      catchError((error: HttpErrorResponse) => {
        if (error.status === 401) {
          // 现在调用的refresh()会返回共享的请求,不会重复发起
          return this.authService.refresh().pipe(
            switchMap(() => {
              const storedTokens = this.authService.getTokens();
              const updatedRequest = request.clone({
                headers: request.headers.set("Authorization", storedTokens.access_token)
              });
              return next.handle(updatedRequest);
            }),
            catchError((refreshError: any) => {
              // 刷新失败,说明refresh token也失效了,直接登出
              this.authService.logout();
              return throwError(refreshError);
            })
          );
        } else if (error.status === 403) {
          this.authService.logout();
        }
        return throwError(() => new Error(error.message));
      })
    );
  }
}

原理说明

  • 当第一个401请求触发refresh()时,refreshTokenRequest会被赋值为当前的HTTP请求Observable,后续的401请求进来时,会直接返回这个已有的Observable,不会再发起新的refresh请求。
  • 当refresh请求完成(不管成功还是失败),finalize操作符会把refreshTokenRequest重置为null,这样下次需要刷新时可以正常发起新的请求。
  • 你的AuthInterceptor逻辑已经处理了refresh请求的token携带(判断url包含/auth/refresh时用refresh_token),这部分不需要修改。

备注:内容来源于stack exchange,提问作者Abhishek Murali

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.20 02:53:02