Angular 17功能拦截器:请求重试与Token刷新实现求助
Angular 15+ 功能型拦截器实现Token刷新与请求重试
原代码核心问题
- 未捕获请求返回的401错误:仅提前检查Token过期,未处理后端返回401的场景
- 错误使用
subscribe:回调内的return next(request)无法传递给拦截器的返回流,导致重试逻辑失效 - 缺少并发请求控制:无全局刷新状态,可能触发重复Token刷新请求
- 变量未定义:
isRefreshing未声明,会引发运行时错误
修正后的完整拦截器代码
import { HttpInterceptorFn, HttpErrorResponse } from '@angular/common/http'; import { inject } from '@angular/core'; import { AuthService } from '../services/auth.service'; import { ILoginResponse } from '../interfaces/iloginresponse'; import { jwtDecode } from 'jwt-decode'; import { catchError, switchMap, tap, finalize, Observable, Subject } from 'rxjs'; // 全局变量控制刷新状态,避免并发请求重复刷新Token let isRefreshing = false; let refreshTokenSubject: Subject<string> = new Subject<string>(); export const apiInterceptor: HttpInterceptorFn = (req, next) => { const authService = inject(AuthService); let request = req.clone(); // 为请求添加Authorization头 const token = sessionStorage.getItem("access_token"); if (token) { request = addAuthHeader(req, token); // 提前检查Token是否过期(可选,配合后端401处理) const tokenData = jwtDecode(token); if (tokenData.exp! < Date.now() / 1000 && !isRefreshing) { return handleTokenRefresh(req, next, authService); } } // 处理请求返回的错误 return next(request).pipe( catchError((error: HttpErrorResponse) => { // 仅处理401未授权错误,且排除刷新Token的请求本身 if (error.status === 401 && req.url !== '/api/auth/refresh-token') { return handleTokenRefresh(req, next, authService); } // 其他错误直接抛出 throw error; }) ); }; // 辅助函数:添加Authorization头 function addAuthHeader(req: any, token: string) { return req.clone({ headers: req.headers.set("Authorization", `Bearer ${token}`) }); } // 核心逻辑:处理Token刷新与请求重试 function handleTokenRefresh(req: any, next: any, authService: AuthService): Observable<any> { if (!isRefreshing) { isRefreshing = true; refreshTokenSubject.next(''); return authService.refreshToken().pipe( tap((data: ILoginResponse) => { const newToken = data.access_token; sessionStorage.setItem("access_token", newToken); refreshTokenSubject.next(newToken); }), switchMap((newTokenData: ILoginResponse) => { // 用新Token重试原请求 return next(addAuthHeader(req, newTokenData.access_token)); }), finalize(() => { isRefreshing = false; }), catchError((error) => { // 刷新Token失败,清空存储并跳转登录页 sessionStorage.removeItem("access_token"); // 这里可以添加跳转到登录页的逻辑,比如router.navigate(['/login']) throw error; }) ); } else { // 已有刷新请求在进行,等待刷新完成后用新Token重试 return refreshTokenSubject.pipe( switchMap((newToken) => { return next(addAuthHeader(req, newToken)); }) ); } }
关键逻辑说明
全局状态控制:
isRefreshing标记当前是否正在刷新Token,避免并发请求重复触发刷新refreshTokenSubject用于在刷新完成后通知等待的请求,统一使用新Token重试
错误拦截与处理:
- 通过
catchError捕获请求返回的HttpErrorResponse,判断是否为401错误 - 排除刷新Token的请求本身,避免死循环
- 通过
Token刷新与重试:
- 刷新Token成功后,更新本地存储的Token,并通过
refreshTokenSubject通知所有等待的请求 - 使用
switchMap切换到重试请求的Observable,确保流的连续性 - 刷新失败时清空Token并处理登录跳转逻辑
- 刷新Token成功后,更新本地存储的Token,并通过
提前Token过期检查:
- 可选逻辑:在发送请求前检查Token是否即将过期,提前触发刷新,避免请求先返回401
内容的提问来源于stack exchange,提问作者RBFraphael
相关产品推荐
相关产品推荐

