简体   繁体   English

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

[英]Is there a method to wait for a refreshed token in okhttp3 interceptor?

Recently I had to work with a big old project that uses Retrofit 1, okhttp3, jobManager and Picasso 2.71828 最近,我不得不处理一个使用Retrofit 1,okhttp3,jobManager和Picasso 2.71828的大型旧项目。

The application receives data from the server. 该应用程序从服务器接收数据。 Interaction logic: the user logs in, receives the token, the refresh token. 交互逻辑:用户登录,接收令牌,刷新令牌。 They are stored in the SharedPreferences with shHelper. 它们通过shHelper存储在SharedPreferences中。 With a token, he can send requests (somewhere he is in the url, and somewhere in the body), with the help of refresh token the user can get a new token if the session is reset or the token is rotten. 使用令牌,他可以发送请求(他在url中的某处,在正文中的某处),借助于刷新令牌,如果会话重置或令牌已损坏,则用户可以获得新的令牌。

Authorization errors (401) are processed by the okhttp3 authenticator, which we managed to use with Picasso. 授权错误(401)由okhttp3身份验证器处理,我们设法将其与Picasso一起使用。 But there was a problem - Picasso if there are several pictures on the screen - sends several requests in succession, simultaneously or almost simultaneously, and since they all immediately receive the answer 401, if the token is rotten, the authenticator immediately sends the same number of requests for updating the token. 但是存在一个问题-毕加索(如果屏幕上有几张图片)-同时或几乎同时连续发送几个请求,并且由于它们都立即收到答复401,因此如果令牌被破坏,身份验证器会立即发送相同的数字请求更新令牌。 Is there some elegant way to wait for the token to be updated and then repeat the requests for the rest of the pictures? 是否有某种优雅的方式来等待令牌更新,然后对其余图片重复请求? Now it happens as follows - having received an error 401, the token is reset to zero (token = "") and all other streams that fall into the authenticator checking if (token == "") execute Thread.sleep () and I am very dissatisfied with it 现在,它发生如下情况-收到错误401,令牌重置为零(令牌=“”),并且所有其他流进入身份验证器的流都检查(令牌==“”)是否执行Thread.sleep()和I对此非常不满意

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();
                }
            }
        };
    }

I'm looking for ways after the first error 401 to send one request to refresh the token and wait for it with all other threads, and then send requests with a new token. 我正在寻找第一个错误401之后的方式,以发送一个刷新令牌的请求并与所有其他线程一起等待,然后发送带有新令牌的请求。 Besides waiting for the updated token in the authenticator, is there any way to simplify this code somehow? 除了等待身份验证器中更新的令牌外,还有什么方法可以某种方式简化此代码? Now this method is about 100 lines long and every time it is necessary to change it - even reading and keeping logic in your head becomes a problem. 现在,这种方法大约有100行,每次需要更改时,即使读取并保持逻辑清晰也成为一个问题。

So, after some time and some tries I made the part of authenticator syncronized on some lock object. 因此,经过一段时间和尝试,我将身份验证器的一部分同步到了一些锁定对象上。 Now only one thread at the time can access authenticator. 现在,当时只有一个线程可以访问身份验证器。 So, if token need to bs refreshed - it will be, and after refreshing all of the threads waiting for new token will repeat their calls with new token. 因此,如果令牌需要刷新-将会刷新,并且在刷新后等待所有新令牌的所有线程将使用新令牌重复其调用。 Thanks @Yuri Schimke for sharing very usefull information. 感谢@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;
                    }
                }
            }
        };
    }

With just OkHttp, you will generally need to handle this complexity in your app whether that is outside the call, in an Authenticator or inside a proactively authenticating Interceptor. 仅使用OkHttp,通常就需要在应用程序中处理这种复杂性,无论是在调用外部,在Authenticator还是在主动身份验证的Interceptor内部。 Concurrency isn't handled for you in these cases either. 在这些情况下,也不为您处理并发。

Discussed here 在这里讨论

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

Make sure to make a synchronous refresh call, as an async call may not have a free thread to execute on. 确保进行同步刷新调用,因为异步调用可能没有空闲线程可以执行。

the answer from @swankjesse was that if you make a sync call in an interceptor, then you are tying up a thread, but won't deadlock because it doesn't need to grab another thread and doesn't hold a lock during that time. @swankjesse的回答是,如果您在拦截器中进行同步调用,则您正在绑一个线程,但不会死锁,因为它不需要抓住另一个线程并且在此期间不持有锁。

Some blogs on similar topics 一些类似主题的博客

https://objectpartners.com/2018/06/08/okhttp-authenticator-selectively-reauthorizing-requests/ 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://medium.com/@sandeeptengale/problem-solved-2-access-token-refresh-with-okhttp-authenticator-5ccb798ede70

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM