Okhttp3拦截器中是否有等待刷新的令牌的方法?

4

最近我必须处理一个使用Retrofit 1, okhttp3, jobManager和Picasso 2.71828的大型旧项目。

应用程序从服务器接收数据。 交互逻辑:用户登录,接收令牌和刷新令牌。它们与shHelper一起存储在SharedPreferences中。有了令牌,他可以发送请求(在某些地方在url中,在某些地方在正文中),借助刷新令牌,用户可以在会话重置或令牌失效时获得新令牌。

由okhttp3验证器处理授权错误(401),我们已经成功将其与Picasso一起使用。 但是出现了一个问题 - 如果屏幕上有多个图片,则Picasso会连续或几乎同时发送多个请求,而且由于它们都立即收到答案401,如果令牌失效,验证器就会立即发送相同数量的更新令牌请求。 是否有一种优雅的方法等待令牌更新然后重复其余图片的请求?现在情况如下 - 收到401错误后,令牌将被重置为零(token =“”),所有其他流都进入验证器检查if(token ==“”),执行Thread.sleep(),我对此非常不满意。

private Authenticator getAuthenticator() {
        return (route, response) -> {
            if (errorCount > 3){
                return null;
            }

            if (response.request().url().toString().endsWith("/refreshToken")) {
                Log.d(TAG, "getAuthenticator: " + "refreshToken");
                PasswordRepeatActivity.start(context);
                return null;
            }

            if (response.request().url().toString().endsWith("/auth")) {
                String message = "Попробуйте позже";
                try {
                    com.google.gson.Gson gson = Gson.builder().create();
                    ApiResponse apiError = gson.fromJson(response.body().string(), ApiResponse.class);
                    message = apiError.getMessage();
                } catch (Exception e) {
                    e.printStackTrace();
                }
                throw new IOException(message);
            }

            String login = spHelper.getCurrentLogin();
            Auth auth = spHelper.getAuth(login);
            String token = auth.getToken();
            HttpUrl oldUrl = response.request().url();

            //if token is empty - repeat checking after some time
            Log.d(TAG, "getAuthenticator: token ==" + token);
            if (token != null && token.isEmpty()) {
                boolean isEmpty = true;
                while (isEmpty){
                    try {
                        Log.d(TAG, "Authenticator: sleeping...");
                        Thread.sleep(500);

                        String mToken = spHelper.getAuth(login).getToken();
                        if (mToken!= null && !mToken.isEmpty()){
                            isEmpty = false;
                        }
                        Log.d(TAG, "Authenticator: check if token is refreshed");
                        if (!mToken.isEmpty() && oldUrl.toString().contains("token") && !mToken.equals(oldUrl.queryParameter("token"))) {
                            Log.d(TAG, "Authenticator: token is valid, token: " + mToken);
                            return getRefreshedUrlRequest(mToken, oldUrl);
                        }
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                        return response.request();
                    }
                }

                return response.request();
            } else if (oldUrl.toString().contains("token") && !token.equals(oldUrl.queryParameter("token"))) {
                Log.d(TAG, "Authenticator: token is valid, token: " + token);
                return getRefreshedUrlRequest(token, oldUrl);
            } else {

                auth.clearToken();
                spHelper.putAuth(login, auth);

                String refreshToken = auth.getRefreshToken();
                RefreshRequest refreshRequest = new RefreshRequest(refreshToken);
                try {
                    AuthResponse refreshResponse = dataApi.refresh(refreshRequest);
                    errorCount = 0;
                    Auth newAuth = refreshResponse.getResponse();
                    spHelper.putAuth(login, newAuth);

                    Request request = response.request();
                    RequestBody requestBody = request.body();
                    String newToken = newAuth.getToken();

                    Log.d(TAG, "Authenticator: token refreshed, old token: " + token + " -> " + "new token : " + newToken);
                    if (oldUrl.toString().contains("token")) {
                        return getRefreshedUrlRequest(newToken, oldUrl);
                    }
                    if (requestBody != null
                            && requestBody.contentType() != null
                            && requestBody.contentType().subtype() != null
                            && requestBody.contentType().subtype().contains("json")) {
                        requestBody = processApplicationJsonRequestBody(requestBody, newToken);
                    }
                    if (requestBody != null) {
                        Request.Builder requestBuilder = request.newBuilder();
                        request = requestBuilder
                                .post(requestBody)
                                .build();
                    } else {
                        LoginActivity.show(context);
                    }
                    return request;
                } catch (RequestException e) {
                    AtlasPatienteLog.d(TAG, "Can't refresh token: " + e.getMessage());
                    return response.request();
                }
            }
        };
    }

我正在寻找在第一个401错误之后发送一个请求以刷新令牌并等待所有其他线程,然后使用新令牌发送请求的方法。

除了在身份验证器中等待更新的令牌外,是否有任何简化此代码的方法?现在,这种方法大约有100行,并且每次都需要更改它 - 即使是在头脑中阅读和保留逻辑也会成为问题。

所以,在一段时间和一些尝试之后,我将身份验证器的一部分同步到某个锁定对象上。现在,只有一个线程可以访问身份验证器。因此,如果需要刷新令牌-将进行刷新,并且在刷新后等待新令牌的所有线程将使用新令牌重复调用他们的请求。

感谢@Yuri Schimke分享非常有用的信息。

private Authenticator getAuthenticator() {
        return (route, response) -> {
            String responseUrl = response.request().url().toString();
            if (responseUrl.endsWith("/refreshToken") ) {
                Log.d(TAG, "getAuthenticator: " + "refreshToken");
                PasswordRepeatActivity.start(context);
                return null;
            }
            if (responseUrl.endsWith("/auth")) {
                String message = "Попробуйте позже";
                try {
                    com.google.gson.Gson gson = Gson.builder().create();
                    ApiResponse apiError = gson.fromJson(response.body().string(), ApiResponse.class);
                    message = apiError.getMessage();
                } catch (Exception e) {
                    e.printStackTrace();
                }
                throw new IOException(message);
            }
            synchronized (LOCK) {
                String login = spHelper.getCurrentLogin();
                Auth auth = spHelper.getAuth(login);
                String token = auth.getToken();
                HttpUrl oldUrl = response.request().url();

                if (oldUrl.toString().contains("token") && !token.equals(oldUrl.queryParameter("token"))) {
                    Log.d(TAG, "Authenticator: token is valid, token: " + token);
                    return getRefreshedUrlRequest(token, oldUrl);
                } else {
                    String refreshToken = auth.getRefreshToken();
                    RefreshRequest refreshRequest = new RefreshRequest(refreshToken);
                    try {
                        AuthResponse refreshResponse = dataApi.refresh(refreshRequest);
                        Auth newAuth = refreshResponse.getResponse();
                        spHelper.putAuth(login, newAuth);

                        Request request = response.request();
                        RequestBody requestBody = request.body();
                        String newToken = newAuth.getToken();

                        Log.d(TAG, "Authenticator: token refreshed, old token: " + token + " -> " + "new token : " + newToken);
                        if (oldUrl.toString().contains("token")) {
                            return getRefreshedUrlRequest(newToken, oldUrl);
                        }
                        if (requestBody != null
                                && requestBody.contentType() != null
                                && requestBody.contentType().subtype() != null
                                && requestBody.contentType().subtype().contains("json")) {
                            requestBody = processApplicationJsonRequestBody(requestBody, newToken);
                        }
                        if (requestBody != null) {
                            Request.Builder requestBuilder = request.newBuilder();
                            request = requestBuilder
                                    .post(requestBody)
                                    .build();
                        } else {
                            LoginActivity.show(context);
                        }
                        return request;
                    } catch (RequestException e) {
                        AtlasPatienteLog.d(TAG, "Can't refresh token: " + e.getMessage());
                        PasswordRepeatActivity.start(context);
                        return null;
                    }
                }
            }
        };
    }

1
欢迎来到Stack Overflow。 进行同步调用client.newCall(request).enqueue(new Callback() {});。请查看https://www.vogella.com/tutorials/JavaLibrary-OkHttp/article.html。 - Farmer
我试图弄清楚如何使用回调函数发出请求,但是验证器应该同步返回一个新的请求或null。如果返回值为零,则我们无法重复使用进入验证器的请求,只能从回调中返回带有刷新令牌的新请求。但是,如果之前返回了null,则不会再次请求。 - Ruslan Sharipov
1个回答

2
仅使用OkHttp,通常需要在应用程序中处理这种复杂性,无论是在调用外部、身份验证器内部还是主动身份验证拦截器内部。在这些情况下,也不会为您处理并发。

在此讨论

https://github.com/square/okhttp/issues/3714#issuecomment-350469364

请注意进行同步刷新调用,因为异步调用可能没有空闲线程来执行。
引用: @swankjesse的回答是,如果在拦截器中进行同步调用,则会绑定一个线程,但不会死锁,因为它不需要获取另一个线程并且在此期间不持有锁。
一些类似主题的博客。

https://objectpartners.com/2018/06/08/okhttp-authenticator-selectively-reauthorizing-requests/

https://medium.com/@sandeeptengale/problem-solved-2-access-token-refresh-with-okhttp-authenticator-5ccb798ede70

https://blog.coinbase.com/okhttp-oauth-token-refreshes-b598f55dd3b2


感谢分享这些资料。 其中一些信息看起来非常有用,我会尝试借助它们改进现有的代码。 - Ruslan Sharipov

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接