HttpInterceptorFn捕获401错误刷新JWT后重试请求失效问题处理
问题描述
我实现了一个拦截器,确保每个请求都携带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
相关产品推荐
相关产品推荐

