Http拦截器遇过期Refresh Token陷入请求循环问题排查
问题描述
Http拦截器多数情况下运行正常,但当Refresh Token过期后,会陷入用过期Refresh Token重复请求更新Access Token的循环,无法触发登出逻辑。后端采用Django/DRF/SimpleJWT,相关代码如下:
拦截器代码
@Injectable() export class AuthInterceptor implements HttpInterceptor { constructor(private inject: Injector){} intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> { let userService = this.inject.get(UserService) let authReq = request; authReq = this.AddTokenHeader(request, userService.getToken()) return next.handle(authReq).pipe( catchError(errordata => { if(errordata.status === 401){ //401 status error, the access token needs refreshing. return this.handleRefreshToken(request, next) } return throwError(() => errordata) }) ) } AddTokenHeader(request: HttpRequest<any>, token: any){ return request.clone({headers: request.headers.set('Authorization', 'Bearer '+token)}) } handleRefreshToken(request: HttpRequest<any>, next: HttpHandler){ let userService = this.inject.get(UserService) return userService.GenerateRefreshToken().pipe( switchMap((data:any) => { userService.updateAccessToken(data) return next.handle(this.AddTokenHeader(request, localStorage.getItem('accessToken'))) }), catchError(errordata => { userService.logout() return throwError(() => errordata) }) ) } }
UserService相关方法
public GenerateRefreshToken(){ return this.http.post(this.djangoPath+'/api/token/refresh/', JSON.stringify({"refresh": localStorage.getItem('refreshToken')}), this.httpOptions) }
原本预期handleRefreshToken中的catchError会处理Refresh Token过期的情况并触发登出,但实际收到重复的401响应,请求陷入循环。
问题分析与解决
核心问题
当Refresh Token过期时,GenerateRefreshToken请求会返回401错误,这个错误会被拦截器顶层的catchError再次捕获,重新进入handleRefreshToken逻辑,形成无限循环——拦截器没有区分原业务请求的401和刷新Token请求的401。
修复步骤
1. 标记刷新Token请求,避免被拦截器重复处理
修改GenerateRefreshToken方法,给请求添加自定义标识,让拦截器识别这是刷新Token的请求,跳过Token添加和错误重试逻辑:
public GenerateRefreshToken(){ const refreshReq = this.http.post( this.djangoPath+'/api/token/refresh/', JSON.stringify({"refresh": localStorage.getItem('refreshToken')}), {...this.httpOptions, headers: {...this.httpOptions.headers, 'skip-interceptor': 'true'}} ); return refreshReq; }
2. 在拦截器中跳过刷新Token请求的处理
在intercept方法开头增加判断,跳过带有标识的刷新请求:
intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> { // 跳过刷新Token的请求,避免循环 if (request.headers.get('skip-interceptor')) { return next.handle(request); } let userService = this.inject.get(UserService) let authReq = request; authReq = this.AddTokenHeader(request, userService.getToken()) return next.handle(authReq).pipe( catchError(errordata => { if(errordata.status === 401){ return this.handleRefreshToken(request, next) } return throwError(() => errordata) }) ) }
3. 确保登出逻辑清空本地Token
在UserService的logout方法中,必须清空本地存储的accessToken和refreshToken,避免后续请求继续使用过期凭证:
logout() { localStorage.removeItem('accessToken'); localStorage.removeItem('refreshToken'); // 补充其他登出逻辑,比如跳转登录页、清除用户状态等 }
修改后,当Refresh Token过期时,GenerateRefreshToken返回的401错误会直接被handleRefreshToken中的catchError捕获,触发登出逻辑清空Token,不会被顶层拦截器重复处理,从而终止循环。
内容的提问来源于stack exchange,提问作者James
相关产品推荐
相关产品推荐

