You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.07 08:31:17