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

HttpInterceptorFn捕获401错误刷新JWT后重试请求失效问题处理

HttpOnly JWT刷新后重试请求未使用新Cookie的问题解决过程

问题描述

我实现了一个拦截器,确保每个请求都携带HttpOnly JWT/refresh令牌。尝试通过捕获401错误来刷新短期JWT,但遇到问题:Cookie刷新成功,但重试请求未使用新Cookie。

通过开发者工具查看网络请求,顺序符合预期,但重试请求未使用新的HttpOnly Cookie(无法通过TypeScript直接修改该Cookie),目标是让失败请求使用刷新后的Cookie重试,后续将考虑缓存多请求并排队重试。

初始代码

export const jwtInterceptor: HttpInterceptorFn = (req, next) => {
  const router: Router = inject(Router);
  const authService: AuthService = inject(AuthService);

  req = req.clone({
    withCredentials: true,
  });

  return next(req).pipe(
    catchError((err: any) => {
      if (err instanceof HttpErrorResponse) {
        // Handle HTTP errors
        if (err.status === 401) {
          // handle unauthorized errors
          console.error('Unauthorized request:', err);
          // retry the request
          authService.refreshToken().subscribe({
            next: () => {
              authService.isRefreshed$.next(true);
            },
            error: () => {
              // Redirect to login page
              authService.logout(true);
              return router.createUrlTree(['/login'], {
                queryParams: { returnUrl: router.url },
              });
            },
          });
          return authService.isRefreshed$.pipe(switchMap(() => next(req)));
        } else {
          // Handle other HTTP error codes
          console.error('HTTP error:', err);
        }
      } else {
        // Handle non-HTTP errors
        console.error('An error occurred:', err);
      }
      // Re-throw the error to propagate it further
      return throwError(() => err);
    })
  );
};

更新1:第一次修改及问题

根据建议修改代码如下,但调用handle401Error处理401错误时,刷新API请求未执行。推测是末尾的return next(req)取消了刷新请求,authService.refreshToken()已进入,但switchMap和catchError未执行。

const handle401Error = (
    isRefreshing: boolean,
    req: HttpRequest<any>,
    next: HttpHandlerFn
  ) => {
    if (!isRefreshing) {
      isRefreshing = true;
      // issue described below was resolved with the return statement
      return authService.refreshToken().pipe(  
        switchMap(() => {                
          isRefreshing = false;
          console.log('Token refreshed successfully.');
          return next(req);
        }),
        catchError((err) => {
          isRefreshing = false;
          console.error('Error refreshing token:', err);
          authService.logout();
          router.navigate(['/login'], { queryParams: { returnUrl: req.url } });
          return throwError(() => err);
        })
      );
    }
    return next(req);
  };

更新2:最终解决方案

最终代码如下,虽刷新过程中仍可能存在失败请求,但不会重复触发刷新操作,所有失败请求都会进行重试:

let refreshTokenInProgress$: Subject<boolean> | null = null;

export const jwtInterceptor: HttpInterceptorFn = (req, next) => {
  const router: Router = inject(Router);
  const authService: AuthService = inject(AuthService);
  req = req.clone({
    withCredentials: true,
  });

  // if token is refreshing wait for it to complete before making the request
  const handle401error = () => {
    if (!refreshTokenInProgress$) {
      refreshTokenInProgress$ = new Subject<boolean>();
      authService.refreshToken().subscribe({
        next: () => {
          refreshTokenInProgress$!.next(true);
        },
        error: () => {
          refreshTokenInProgress$!.next(false);
          authService.logout();
          router.navigate(['/login']);
        },
        complete: () => {
          refreshTokenInProgress$!.complete();
          refreshTokenInProgress$ = null;
        },
      });
    }
  };

  return next(req).pipe(
    catchError((err: any) => {
      if (err instanceof HttpErrorResponse) {
        if (err.status === 401) {
          handle401error();
          return refreshTokenInProgress$!.pipe(switchMap(() => next(req)));
        }
      }
      return throwError(() => err);
    })
  );
};

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.16 13:01:03