当前位置: 首页 > 知识库问答 >
问题:

当向服务器发送多个请求时,Okhttp刷新过期令牌

甄飞飙
2023-03-14

目前,我有一个变量来指示标记刷新是否在Authenticator中进行,在这种情况下,我取消Interceptor中的所有后续请求,用户必须手动刷新页面,否则我可以注销用户并强制用户登录。

对于上述问题,使用OKHTTP3.x for Android有什么好的解决方案或架构?

编辑:我想解决的问题是一般的,我不想顺序我的电话。也就是说,等待一个调用完成并刷新令牌,然后只在活动和片段级别上发送请求的其余部分。

public class CustomAuthenticator implements Authenticator {

    @Inject AccountManager accountManager;
    @Inject @AccountType String accountType;
    @Inject @AuthTokenType String authTokenType;

    @Inject
    public ApiAuthenticator(@ForApplication Context context) {
    }

    @Override
    public Request authenticate(Route route, Response response) throws IOException {

        // Invaidate authToken
        String accessToken = accountManager.peekAuthToken(account, authTokenType);
        if (accessToken != null) {
            accountManager.invalidateAuthToken(accountType, accessToken);
        }
        try {
                // Get new refresh token. This invokes custom AccountAuthenticator which makes a call to get new refresh token.
                accessToken = accountManager.blockingGetAuthToken(account, authTokenType, false);
                if (accessToken != null) {
                    Request.Builder requestBuilder = response.request().newBuilder();

                    // Add headers with new refreshToken

                    return requestBuilder.build();
            } catch (Throwable t) {
                Timber.e(t, t.getLocalizedMessage());
            }
        }
        return null;
    }
}

共有1个答案

松兴邦
2023-03-14

需要注意的是,AccountManager.BlockingGetAuthToken(或非阻塞版本)仍然可以在其他地方调用,而不是拦截器。因此,防止此问题发生的正确位置是在身份验证器中。

我们希望确保需要访问令牌的第一个线程将检索它,可能的其他线程应该注册回调,以便在第一个线程完成检索令牌时调用。
好消息是,AbstractAccountAuthEnticator已经有一种传递异步结果的方法,即AccountAuthEnticatorResponse,您可以在该方法上调用onResultonError

下面的示例由3个块组成。

第一个是关于确保只有一个线程获取访问令牌,而其他线程只是注册它们的响应以进行回调。

第二部分只是一个虚拟的空结果包。在这里,您将加载您的令牌,可能刷新它,等等。

第三部分是一旦你有了你的结果(或错误)你会做什么。您必须确保为可能注册的每个其他线程调用响应。

boolean fetchingToken;
List<AccountAuthenticatorResponse> queue = null;

@Override
public Bundle getAuthToken(AccountAuthenticatorResponse response, Account account, String authTokenType, Bundle options) throws NetworkErrorException {

  synchronized (this) {
    if (fetchingToken) {
      // another thread is already working on it, register for callback
      List<AccountAuthenticatorResponse> q = queue;
      if (q == null) {
        q = new ArrayList<>();
        queue = q;
      }
      q.add(response);
      // we return null, the result will be sent with the `response`
      return null;
    }
    // we have to fetch the token, and return the result other threads
    fetchingToken = true;
  }

  // load access token, refresh with refresh token, whatever
  // ... todo ...
  Bundle result = Bundle.EMPTY;

  // loop to make sure we don't drop any responses
  for ( ; ; ) {
    List<AccountAuthenticatorResponse> q;
    synchronized (this) {
      // get list with responses waiting for result
      q = queue;
      if (q == null) {
        fetchingToken = false;
        // we're done, nobody is waiting for a response, return
        return null;
      }
      queue = null;
    }

    // inform other threads about the result
    for (AccountAuthenticatorResponse r : q) {
      r.onResult(result); // return result
    }

    // repeat for the case another thread registered for callback
    // while we were busy calling others
  }
}

只要确保在使用响应时在所有路径上返回null即可。

显然,您可以使用其他方法来同步这些代码块,比如@Matrix在另一个响应中所示的atomics。我使用了synchronized,因为我认为这是最容易掌握的实现,因为这是一个很好的问题,每个人都应该这样做;)

上面的示例是这里描述的发射器循环的一个改编版本,其中详细介绍了并发性。如果您对RxJava的工作原理感兴趣,这个博客是一个很好的来源。

 类似资料:
  • 我试图从比特币市场RESTful API中获取不同的JSON。 问题是:我只能将单个GET请求逐个发送到API,因此我无法同时从所有比特币市场获取所有数据。 有没有办法使用Python线程(每个线程使用不同的客户端端口发送GET请求)同时获取多个数据?

  • XMLHttpRequest 对象用于和服务器交换数据。 向服务器发送请求 如需将请求发送到服务器,我们使用 XMLHttpRequest 对象的 open() 和 send() 方法:xmlhttp.open("GET","ajax_info.txt",true); xmlhttp.send(); 方法 描述 open(method,url,async) 规定请求的类型、URL 以及是否异步处理

  • 下面是官方指南中的GCM Android集成示例。 特别是,我对上述链接类中的以下行感到困惑: 现在,每当我的主要活动启动时,我都会调用intent服务,我相信instanceID负责启动令牌刷新。 我是否应该在每次从我的主要活动启动此GCM注册意图时检查Shared Prefs值。但是,在这种情况下刷新将失败,因为在初始令牌获取之后,条件将始终为true。 我应该放弃共享prefs逻辑吗?这样每

  • 我的一个EC2实例上有一个graphql服务器正在运行。我也有AWS appsync运行,但目前它只与几个Lambda集成。 我想将我的Appsync连接到graphql服务器,这样Appsync将作为特定查询/变化的代理。 因此,从客户端来看,它将如下所示: 客户端将一个查询发送到APPESNC,让我们假设它看起来像这样: Appsync已经定义了一个查询,它被配置为在graphql服务器上代理

  • 在行引发异常: 线程“main”javax.net.ssl.SSLHandShaker异常:Sun.Security.Validator.ValidatoreXception:PKIX路径构建失败:Sun.Security.Provider.CertPath.SunCertPathBuilderException:无法在Sun.Security.SSL.Alerts.GetSleXception(

  • 我想我调用AWS存储网关refreshCache方法的频率太高了(如消息所示),但我不确定我需要等待多长时间,直到我再次点击它,任何帮助都将是感激的。 com.amazonaws.services.StorageGateway.model.InvalidGatewayRequestException:向服务器发送的请求太多。(服务:AwsStorageGateway;状态代码:400;错误代码:I