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

并行API触发401时如何避免重复刷新Token并实现同步?

解决并行请求下重复刷新Token的问题

这个问题在并发REST请求场景下非常典型——多个请求同时触发401,导致重复发起Token刷新,既浪费资源还可能引发后端的刷新频率限制。核心思路是让刷新操作互斥,同时让等待的请求共享刷新后的Token结果,下面给你具体的实现方案:

1. 基于互斥锁的同步方案(适合Java等后端场景)

在Interceptor中维护一个全局的互斥锁,确保同一时间只有一个请求能执行刷新Token的逻辑,其他请求等待锁释放后直接复用新Token。

关键实现要点:

  • 用全局锁控制刷新入口
  • 加锁后二次检查Token状态(避免等待期间Token已被更新)
  • 用try-finally确保锁一定会释放

示例代码(Spring Boot拦截器场景):

import java.io.IOException;
import java.util.Objects;
import java.util.concurrent.locks.ReentrantLock;
import okhttp3.Request;
import okhttp3.Response;
import okhttp3.Interceptor;

public class AuthInterceptor implements Interceptor {
    // 全局互斥锁,确保单例(可通过Spring @Bean注入)
    private final ReentrantLock refreshLock = new ReentrantLock();
    // 用volatile保证多线程下Token的可见性
    private volatile String currentToken;
    // 假设从存储获取Token的方法
    private String getTokenFromStorage() { /* ... */ return currentToken; }
    private void saveTokenToStorage(String token) { /* ... */ currentToken = token; }
    // 刷新Token的核心方法
    private String refreshToken() throws IOException { /* 调用刷新接口获取新Token */ }

    @Override
    public Response intercept(Chain chain) throws IOException {
        Request originalRequest = chain.request();
        // 先携带当前Token发起请求
        Response response = proceedWithToken(originalRequest, currentToken);

        if (response.code() == 401) {
            refreshLock.lock();
            try {
                // 二次检查:防止等待期间已有其他请求完成了Token刷新
                String latestToken = getTokenFromStorage();
                if (!Objects.equals(currentToken, latestToken)) {
                    currentToken = latestToken;
                    // 用最新Token重试请求
                    return proceedWithToken(originalRequest, currentToken);
                }

                // 执行Token刷新逻辑
                String newToken = refreshToken();
                currentToken = newToken;
                saveTokenToStorage(newToken);

                // 用新Token重试当前请求
                return proceedWithToken(originalRequest, newToken);
            } finally {
                // 无论刷新成功失败,都释放锁
                refreshLock.unlock();
            }
        }

        return response;
    }

    // 封装携带Token发起请求的逻辑
    private Response proceedWithToken(Request request, String token) throws IOException {
        Request authRequest = request.newBuilder()
                .header("Authorization", "Bearer " + token)
                .build();
        return chain.proceed(authRequest);
    }
}

2. 基于异步结果共享的方案(适合前端或异步后端场景)

如果是JavaScript前端或者用CompletableFuture的异步后端,可以通过维护一个全局的"刷新Promise/Future"来共享刷新结果,避免重复发起刷新。

关键实现要点:

  • 全局变量存储正在进行的刷新Promise
  • 刷新完成后清空Promise,允许下次触发
  • 所有等待的请求共享同一个刷新结果

示例代码(JavaScript前端拦截器场景):

// 全局变量:存储正在进行的刷新Promise,初始为null
let refreshPromise = null;
// 当前Token,假设从localStorage获取
let currentToken = localStorage.getItem("token");

async function authInterceptor(requestConfig) {
    // 先携带当前Token发起请求
    let response = await fetch(requestConfig.url, {
        ...requestConfig,
        headers: {
            ...requestConfig.headers,
            Authorization: `Bearer ${currentToken}`
        }
    });

    if (response.status === 401) {
        // 检查是否已有正在进行的刷新操作
        if (!refreshPromise) {
            // 发起刷新Token请求,封装为Promise
            refreshPromise = fetch("/api/refresh-token", {
                method: "POST",
                headers: { "Content-Type": "application/json" },
                body: JSON.stringify({ refreshToken: localStorage.getItem("refreshToken") })
            })
            .then(res => res.json())
            .then(data => {
                // 更新Token并存储
                currentToken = data.accessToken;
                localStorage.setItem("token", currentToken);
                return currentToken;
            })
            .catch(err => {
                // 刷新失败:清空Promise,抛出错误让上层处理
                refreshPromise = null;
                throw new Error("Token刷新失败");
            })
            .finally(() => {
                // 无论成功失败,都清空刷新Promise
                refreshPromise = null;
            });
        }

        // 等待刷新完成,获取新Token
        const newToken = await refreshPromise;
        // 用新Token重试请求
        return fetch(requestConfig.url, {
            ...requestConfig,
            headers: {
                ...requestConfig.headers,
                Authorization: `Bearer ${newToken}`
            }
        });
    }

    return response;
}

3. 额外注意事项

  • 避免无限循环:刷新Token后重试如果还是返回401,大概率是权限不足或Refresh Token过期,此时应该直接返回错误,不要继续重试。
  • 锁的全局唯一性:确保锁或刷新Promise是全局单例,比如后端用Spring的单例Bean,前端用模块级变量。
  • 异常处理:刷新Token失败时,一定要正确释放锁或清空Promise,避免后续请求一直处于等待状态。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.28 09:54:14