优化Angular HTTP拦截器:移除刷新令牌请求检查避免无限循环
无需显式检查刷新令牌端点,解决Angular拦截器无限循环问题
要去掉硬编码的isRefreshTokenRequest检查同时避免无限循环,核心思路是让拦截器能识别出“刷新令牌请求”并跳过认证逻辑,推荐用Angular HttpContext自定义标记的方案——这种方式不依赖端点URL,更灵活且不会把标记暴露给服务器。
步骤1:给刷新请求添加跳过拦截器的标记
在你的AuthService里,定义一个全局的HttpContext标记,然后在refreshToken方法创建请求时带上这个标记:
// auth.service.ts import { HttpClient, HttpContext, HttpRequest } from '@angular/common/http'; import { Injectable } from '@angular/core'; import { Observable } from 'rxjs'; import { AuthResponse } from '../interfaces/auth-response'; @Injectable({ providedIn: 'root' }) export class AuthService { constructor(private httpClient: HttpClient) {} // 定义自定义上下文标记,默认值为false static readonly SKIP_AUTH_INTERCEPTOR = new HttpContextToken<boolean>(() => false); refreshToken(): Observable<AuthResponse> { const refreshToken = this.getRefreshToken(); // 替换成你实际获取refresh token的逻辑 const request = new HttpRequest( 'POST', '/api/authentication/refresh-token', { refreshToken }, { // 给该请求添加跳过拦截器的标记 context: new HttpContext().set(AuthService.SKIP_AUTH_INTERCEPTOR, true) } ); return this.httpClient.request<AuthResponse>(request); } // 其他已有的方法:accessToken(), getRefreshToken(), logOut()... }
步骤2:修改拦截器,通过标记判断是否跳过处理
移除原来的isRefreshTokenRequest函数,替换为检查自定义标记的逻辑:
// auth.interceptor.ts import { inject } from '@angular/core'; import { HttpErrorResponse, HttpEvent, HttpHandlerFn, HttpRequest } from '@angular/common/http'; import { BehaviorSubject, catchError, filter, Observable, switchMap, take, throwError } from 'rxjs'; import { AuthService } from '../services/auth/auth.service'; import { AuthResponse } from '../interfaces/auth-response'; let isRefreshing = false; const refreshTokenSubject: BehaviorSubject<string | null> = new BehaviorSubject<string | null>(null); export function authInterceptor(request: HttpRequest<unknown>, next: HttpHandlerFn): Observable<HttpEvent<unknown>> { const authService = inject(AuthService); // 检查请求是否带有跳过拦截器的标记,是则直接放行 if (request.context.get(AuthService.SKIP_AUTH_INTERCEPTOR)) { return next(request); } // 给普通请求添加access token if (authService.accessToken()) { request = addToken(request, authService.accessToken()!); } function handle401Error(request: HttpRequest<unknown>, next: HttpHandlerFn): Observable<HttpEvent<unknown>> { if (!isRefreshing) { isRefreshing = true; refreshTokenSubject.next(null); return authService.refreshToken().pipe( switchMap((response: AuthResponse) => { isRefreshing = false; refreshTokenSubject.next(response.accessToken); return next(addToken(request, response.accessToken)); }), catchError((error) => { isRefreshing = false; if ( error instanceof HttpErrorResponse && error.status === 401 && (error.error?.message === 'Token expired' || error.error?.message === 'Invalid token') ) { authService.logOut(); } return throwError(() => error); }), ); } return refreshTokenSubject.pipe( filter((token) => token !== null), take(1), switchMap((token) => next(addToken(request, token!))), ); } return next(request).pipe( catchError((error: HttpErrorResponse) => { if (error.status === 401) { return handle401Error(request, next); } return throwError(() => error); }), ); } function addToken(request: HttpRequest<unknown>, token: string) { return request.clone({ setHeaders: { Authorization: `Bearer ${token}`, }, }); }
为什么能避免无限循环?
- 标记识别:刷新令牌请求带有
SKIP_AUTH_INTERCEPTOR标记,拦截器会直接放行,不会给它添加access token,也不会进入401错误处理逻辑。 - 状态锁控制:全局的
isRefreshing状态确保同一时间只有一个刷新请求在执行,就算出现意外情况,也不会重复触发刷新流程。
内容的提问来源于stack exchange,提问作者Muhammad-Ali
相关产品推荐
相关产品推荐

