我创建了一个拦截器来确保每个请求都发送 HttpOnly jwt/refresh 令牌。我试图通过捕获 401 错误并请求服务器刷新来刷新我短暂的 jwt。它似乎正在工作,尽管可能不像我预期的那样。 这是我的代码:
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);
})
);
};
当我通过开发人员工具查看网络调用时,看起来调用是按照我期望的顺序进行的。 cookie 已成功刷新,但未在重试请求中使用。我无法通过 TS 使用 HttpOnly cookie 修改请求,所以我不确定如何继续。
目标是使用刷新的 cookie 重试失败的请求。将来我会考虑缓存多个请求并在队列中重试它们。
您代码中的问题是订阅中的代码是
asynchoronous
,而其下面的代码是synchoronous
,因此底部代码不会等待主题具有值。
而是尝试下面的方法,他们使用
switchMap
来刷新单个流中的令牌(这消除了这个问题,因为 switchMap 将使流等待可观察到的刷新)在流的末尾,我们再次调用next(req)
重新启动 API 调用或在刷新令牌失败时抛出错误。
确保仅在用户登录时调用刷新令牌
this.storageService.isLoggedIn()
,如果未经授权,则无需刷新逻辑。
您可以使用
bezkoder
中的本教程,指导您如何添加 refreshToken
逻辑。
带有拦截器和 JWT 的 Angular 12 刷新令牌示例(从链接中选择适当的 Angular 版本)
Bezkoder Github - http.interceptor.ts
export const jwtInterceptor: HttpInterceptorFn = (req, next) => {
const router: Router = inject(Router);
const authService: AuthService = inject(AuthService);
let isRefreshing = false;
req = req.clone({
withCredentials: true,
});
const handle401Error = (isRefreshing: boolean, req: HttpRequest<any>, next: HttpHandler) => {
if (isRefreshing) {
isRefreshing = true;
if (this.storageService.isLoggedIn()) {
return this.authService.refreshToken().pipe(
switchMap(() => {
isRefreshing = false;
return next(req);
}),
catchError((error) => {
isRefreshing = false;
// if (error.status == '403') {
// this.eventBusService.emit(new EventData('logout', null));
// }
return throwError(() => error);
})
);
}
}
return next(req);
};
return next(req).pipe(
catchError((error) => {
if (
error instanceof HttpErrorResponse &&
!req.url.includes('auth/signin') &&
error.status === 401
) {
return handle401Error(isRefreshing, req, next);
}
return throwError(() => error);
})
);
};