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

Angular拦截器调用JWT刷新API触发无限循环问题排查

问题原因及解决方法

无限循环的核心原因是:刷新令牌的请求也会被你的拦截器捕获。因为refreshToken请求的URL符合isApiUrl的判断条件,而此时旧的access token已经过期,拦截器会再次触发令牌刷新逻辑,形成无限递归。

解决步骤

1. 让刷新令牌请求跳过拦截器处理

你需要给刷新请求添加一个“豁免标识”,让拦截器识别并直接放行,不执行认证逻辑。有两种常用方式:

方式一:通过请求URL判断

在拦截器最开头,先检查当前请求是否是刷新令牌的接口,如果是直接放行:

intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
    // 先判断是否是刷新令牌请求,直接放行
    const isRefreshTokenRequest = request.url.includes('/authentication/refresh-token');
    if (isRefreshTokenRequest) {
        return next.handle(request);
    }

    // 以下是原来的认证逻辑
    const token = this.localStorageService.token;
    const isApiUrl = request.url.startsWith(environment.apiUrl);

    if (token && isApiUrl) {
        if (this.jwtHelperService.isTokenExpired(token)) {
            return from(this.refreshToken()).pipe(mergeMap((response) => {
                if (response) {
                    localStorage.setItem(LocalStorageKeys.TOKEN, JSON.stringify(response));
                    request = request.clone({
                        setHeaders: {
                            Authorization: `Bearer ${response}`
                        }
                    });
                    return next.handle(request);
                } else {
                    this.authenticationService.logout();
                    return next.handle(request);
                }
            }));
        } else {
            request = request.clone({
                setHeaders: {
                    Authorization: `Bearer ${token}`
                }
            });
            return next.handle(request);
        }
    }

    return next.handle(request);
}

方式二:通过自定义请求头判断

在refreshToken方法的请求中添加自定义头,然后拦截器检查这个头并放行:

修改refreshToken方法:

async refreshToken(): Promise<string> {
    const res$ = this.http
        .post(`${environment.apiUrl}/authentication/refresh-token`,
            { token: this.localStorageService.token, refreshToken: this.localStorageService.refreshToken },
            // 添加自定义请求头标识
            { headers: new HttpHeaders({ 'Skip-Auth-Interceptor': 'true' }) })
        .pipe(map((res) => res['data']['token']))
        .pipe(first());

    const response = await lastValueFrom(res$);
    return response;
}

修改拦截器开头逻辑:

intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
    // 检查是否有跳过拦截的标识
    const skipAuthInterceptor = request.headers.has('Skip-Auth-Interceptor');
    if (skipAuthInterceptor) {
        // 移除自定义头(可选,避免传给后端)
        const clonedRequest = request.clone({ headers: request.headers.delete('Skip-Auth-Interceptor') });
        return next.handle(clonedRequest);
    }

    // 以下是原来的认证逻辑
}

2. 额外注意事项

  • 确保后端的刷新令牌接口不需要携带有效的access token,仅需要refresh token即可完成验证,否则刷新请求会被后端拒绝。
  • 建议在拦截器中添加“正在刷新令牌”的状态锁,避免同一时间多个过期请求触发多次刷新操作。

内容的提问来源于stack exchange,提问作者Pawan Nogariya

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.14 19:37:06