OkHttp 和 Retrofit,用并发请求刷新令牌

Posted

技术标签:

【中文标题】OkHttp 和 Retrofit,用并发请求刷新令牌【英文标题】:OkHttp and Retrofit, refresh token with concurrent requests 【发布时间】:2017-11-07 06:38:10 【问题描述】:

在我的应用程序中,我实现了 Retrofit 来调用 WebServices,并且我使用 OkHttp 来使用拦截器和身份验证器。有些请求需要token,我已经实现了Authenticator接口来处理刷新(遵循官方documentation)。但我有以下问题:有时在我的应用程序中,我必须一次调用多个请求。因此,对于其中一个,我将遇到 401 错误。

这是我的请求调用代码:

public static <S> S createServiceAuthentication(Class<S> serviceClass, boolean hasPagination) 

        final String jwt = JWT.getJWTValue(); //Get jwt value from Realm

        if (hasPagination) 
            Gson gson = new GsonBuilder().
                    registerTypeAdapter(Pagination.class, new PaginationTypeAdapter()).create();

            builder =
                    new Retrofit.Builder()
                            .baseUrl(APIConstant.API_URL)
                            .addConverterFactory(GsonConverterFactory.create(gson));
        

        OkHttpClient.Builder httpClient =
                new OkHttpClient.Builder();

        httpClient.addInterceptor(new AuthenticationInterceptor(jwt));
        httpClient.authenticator(new Authenticator() 
            @Override
            public Request authenticate(Route route, Response response) throws IOException 
                if (responseCount(response) >= 2) 
                    // If both the original call and the call with refreshed token failed,
                    // it will probably keep failing, so don't try again.
                    return null;
                

                if (jwt.equals(response.request().header("Authorization"))) 
                    return null; // If we already failed with these credentials, don't retry.
                

                APIRest apiRest = createService(APIRest.class, false);
                Call<JWTResponse> call = apiRest.refreshToken(new JWTBody(jwt));
                try 
                    retrofit2.Response<JWTResponse> refreshTokenResponse = call.execute();
                    if (refreshTokenResponse.isSuccessful()) 

                        JWT.storeJwt(refreshTokenResponse.body().getJwt());

                        return response.request().newBuilder()
                                .header(CONTENT_TYPE, APPLICATION_JSON)
                                .header(ACCEPT, APPLICATION)
                                .header(AUTHORIZATION, "Bearer " + refreshTokenResponse.body().getJwt())
                                .build();
                     else 
                        return null;
                    
                 catch (IOException e) 
                    return null;
                
            
        );

        builder.client(httpClient.build());
        retrofit = builder.build();

        return retrofit.create(serviceClass);
    

    private static int responseCount(Response response) 
        int result = 1;
        while ((response = response.priorResponse()) != null) 
            result++;
        
        return result;
    

问题很简单,第一个请求会成功刷新令牌,但其他请求会失败,因为他们会尝试刷新已刷新的令牌。 WebService 返回错误 500。有什么优雅的解决方案可以避免这种情况吗?

谢谢!

【问题讨论】:

这可能对你有帮助,希望还为时不晚***.com/a/48518733/8187386 【参考方案1】:

如果我理解您的问题,在更新令牌时会发送一些请求,这会给您一个错误。

您可以尝试在更新令牌时阻止所有请求(使用“同步”对象),但这不会涵盖已发送请求的情况。

由于这个问题很难完全避免,也许这里的正确方法是有一个好的后备行为。例如,通过使用更新的令牌重新运行请求来处理您在令牌更新期间发出请求时收到的错误。

【讨论】:

【参考方案2】:

写服务。

public class TokenService extends Service 

private static final String TAG = "HelloService";

private boolean isRunning = false;
OkHttpClient client;
JSONObject jsonObject;
public static String URL_LOGIN = "http://server.yoursite";
String phone_number, password;

@Override
public void onCreate() 
    Log.i(TAG, "Service onCreate");
    jsonObject = new JSONObject();
    client = new OkHttpClient();

    SharedPreferences pref_phone = getSharedPreferences("Full_Phone", MODE_PRIVATE);
    phone_number = pref_phone.getString("Phone", "");

    SharedPreferences pref_password = getSharedPreferences("User_Password", MODE_PRIVATE);
    password = pref_password.getString("Password", "");

    isRunning = true;


@Override
public int onStartCommand(Intent intent, int flags, int startId) 

    Log.i(TAG, "Service onStartCommand");
    try 
        jsonObject.put("phone_number", phone_number);
        jsonObject.put("password", password);

     catch (JSONException e) 
        e.printStackTrace();
    
    new Thread(new Runnable() 
        @Override
        public void run() 
            for (; ; ) 
                try 
                    Thread.sleep(1000 * 60 * 2);
                 catch (Exception e) 
                

                if (isRunning) 

                    AsyncTaskRunner myTask = new AsyncTaskRunner();
                    myTask.execute();

                 else 

                    Log.d("CHECK__", "Check internet connection");

                
            
        
    ).start();

    return Service.START_STICKY;


@Override
public IBinder onBind(Intent arg0) 
    Log.i(TAG, "Service onBind");
    return null;


@Override
public void onDestroy() 

    isRunning = false;
    Log.i(TAG, "Service onDestroy");


String post(String url, JSONObject login) 
    try 
        MediaType mediaType = MediaType.parse("application/json");
        RequestBody body = RequestBody.create(mediaType, login.toString());
        okhttp3.Request request = new okhttp3.Request.Builder()
                .url(url)
                .post(body)
                .build();

        okhttp3.Response response = client.newCall(request).execute();

        try 
            return response.body().string();
         catch (IOException e) 
            e.printStackTrace();
        
     catch (Exception e) 
        e.printStackTrace();
    
    return "";


String response;

private class AsyncTaskRunner extends AsyncTask<String, String, String> 

    @Override
    protected void onPreExecute() 
    

    @Override
    protected String doInBackground(String... params) 

        try 
            response = post(
                    URL_LOGIN, jsonObject);

         catch (Exception e) 
            e.printStackTrace();
        
        return null;
    

    @Override
    protected void onPostExecute(String result) 
        Log.d("---OKHTTP---", response);
    

【讨论】:

我能理解您的回复吗?对我来说,这完全超出了主题。此外,我正在使用我说的改造,而不是 AyncTask

以上是关于OkHttp 和 Retrofit,用并发请求刷新令牌的主要内容,如果未能解决你的问题,请参考以下文章

Okhttp + Retrofit @Body 请求 - 传输编码:添加了分块

OkHttp 和 Retrofit 无限 Post 请求在后台

带你一步步剖析Retrofit 源码解析:一款基于 OkHttp 实现的网络请求框架

retrofit和okhttp请求url的参数拼接

真的有必要用rxjava吗

Retrofit+OKHttp 教你怎么持久化管理Cookie