身份验证 – 在使用Volley重试旧请求之前重新获取新令牌

我有一个使用Volley实现的简单身份验证系统.它是这样的:
登录时从服务器获取令牌 – >一小时后,此令牌过期 – >当它到期时,我们会发现API调用失败,所以我们应该(在重试时) – >在该呼叫失败时获取新令牌,然后 – >重试原始呼叫.

我已经实现了这个,并且令牌成功返回,但是因为我认为我对Volley RequestQueue做错了,原始请求在新的有效令牌能够被使用之前使用了所有它的重试.请参阅以下代码

public class GeneralAPICall extends Request<JSONObject> {
public static String LOG_TAG = GeneralAPICall.class.getSimpleName();

SessionManager sessionManager; //instance of sessionManager needed to get user's credentials
private Response.Listener<JSONObject> listener; //the response listener used to deliver the response
private Map<String,String> headers = new HashMap<>(); //the headers used to authenticate
private Map<String,String> params; //the params to pass with API call,can be null

public GeneralAPICall(int method,String url,Map<String,String> params,Context context,Response.Listener<JSONObject> responseListener,Response.ErrorListener errorListener) {
    super(method,url,errorListener);
    sessionManager = new SessionManager(context); //instantiate
    HashMap<String,String> credentials = sessionManager.getUserDetails(); //get the user's credentials for authentication
    this.listener = responseListener;
    this.params = params;
    //encode the user's username and token
    String loginEncoded = new String(Base64.encode((credentials.get(Constants.SessionManagerConstants.KEY_USERNAME)
            + Constants.APIConstants.Characters.CHAR_COLON
            + credentials.get(Constants.SessionManagerConstants.KEY_TOKEN)).getBytes(),Base64.NO_WRAP));
    Log.v(LOG_TAG,loginEncoded); //Todo: remove
    this.headers.put(Constants.APIConstants.BasicAuth.AUTHORIZATION,Constants.APIConstants.BasicAuth.BASIC + loginEncoded); //set the encoded information as the header
    setRetryPolicy(new TokenRetryPolicy(context)); //**THE RETRY POLICY**
}

我设置的重试策略被定义为认值,但我实现了自己的重试方法

@Override
public void retry(VolleyError error) throws VolleyError {
    Log.v(LOG_TAG,"Initiating a retry");
    mCurrentRetryCount++; //increment our retry count
    mCurrentTimeoutMs += (mCurrentTimeoutMs * mBackoffMultiplier);
    if (error instanceof AuthFailureError) { //we got a 401,and need a new token
        Log.v(LOG_TAG,"AuthFailureError found!");
        VolleyUser.refreshTokenTask(context,this); //**GET A NEW TOKEN**
    }
    if (!hasAttemptRemaining()) {
        Log.v(LOG_TAG,"No attempt remaining,ERROR");
        throw error;
    }
}

刷新令牌任务定义RefreshAPICall

public static void refreshTokenTask(Context context,IRefreshTokenReturn listener) {
    Log.v(LOG_TAG,"refresh token task called");
    final IRefreshTokenReturn callBack = listener;

    RefreshAPICall request = new RefreshAPICall(Request.Method.GET,Constants.APIConstants.URL.GET_TOKEN_URL,context,new Response.Listener<JSONObject>() {

        @Override
        public void onResponse(JSONObject response) {
            try {
                String token = response.getString(Constants.APIConstants.Returns.RETURN_TOKEN);
                Log.v(LOG_TAG,"Token from return is: " + token);
                callBack.onTokenRefreshComplete(token);
            } catch (JSONException e) {
                callBack.onTokenRefreshComplete(null); //Todo: log this
                e.printstacktrace();
            }
        }
    },new Response.ErrorListener() {
        @Override
        public void onErrorResponse(VolleyError error) {
            Log.v(LOG_TAG,"Error with RETRY : " + error.toString());
        }
    });

    VolleySingleton.getInstance(context).addToRequestQueue(request);
}

我们的RefreshAPICall定义:

public RefreshAPICall(int method,String> credentials = sessionManager.getRefreshUserDetails(); //get the user's credentials for authentication
    this.listener = responseListener;
    //encode the user's username and token
    String loginEncoded = new String(Base64.encode((credentials.get(Constants.SessionManagerConstants.KEY_USERNAME)
            + Constants.APIConstants.Characters.CHAR_COLON
            + credentials.get(Constants.SessionManagerConstants.KEY_PASSWORD)).getBytes(),Base64.NO_WRAP));
    this.headers.put(Constants.APIConstants.BasicAuth.AUTHORIZATION,Constants.APIConstants.BasicAuth.BASIC + loginEncoded); //set the encoded information as the header
    setTag(Constants.VolleyConstants.RETRY_TAG); //mark the retry calls with a tag so we can delete any others once we get a new token
    setPriority(Priority.IMMEDIATE); //set priority as immediate because this needs to be done before anything else

    //debug lines
    Log.v(LOG_TAG,"RefreshAPICall made with " + credentials.get(Constants.SessionManagerConstants.KEY_USERNAME) + " " +
            credentials.get(Constants.SessionManagerConstants.KEY_PASSWORD));
    Log.v(LOG_TAG,"Priority set on refresh call is " + getPriority());
    Log.v(LOG_TAG,"Tag for Call is " + getTag());
}

我将此请求的优先级设置为高,以便在失败之前触发,因此一旦我们获得令牌,原始调用就可以使用有效令牌触发.

最后,在响应时,我使用重试标记删除任何其他任务(如果多个API调用失败并进行多次重试调用,我们不希望多次覆盖新令牌)

@Override
public void onTokenRefreshComplete(String token) {
    VolleySingleton.getInstance(context).getRequestQueue().cancelAll(Constants.VolleyConstants.RETRY_TAG);
    Log.v(LOG_TAG,"Cancelled all retry calls");
    SessionManager sessionManager = new SessionManager(context);
    sessionManager.setStoredToken(token);
    Log.v(LOG_TAG,"Logged new token");
}

不幸的是,LogCat告诉我在使用令牌之前所有的重试都在进行.令牌成功返回,但显然IMMEDIATE优先级对队列调度调用的顺序没有影响.

有关如何确保我的RefreshAPICall的任何帮助都会被激发,然后才会非常感谢其他任务.我想知道Volley是否将RefreshAPICall视为原始失败任务的子任务,因此它会尝试将该原始任务调用其重试次数,直到这些任务结束,然后触发RefreshAPICall.

LogCat(不知道如何让它看起来很漂亮):

05-05 16:12:07.145: E/Volley(1972): [137] BasicNetwork.performRequest: 
Unexpected response code **401 for https://url.me/api/get_friends**
05-05 16:12:07.145: V/TokenRetryPolicy(1972): Initiating a retry
05-05 16:12:07.145: V/TokenRetryPolicy(1972): AuthFailureError found!
05-05 16:12:07.146: V/VolleyUser(1972): refresh token task called
05-05 16:12:07.146: V/RefreshAPICall(1972): RefreshAPICall made with username user_password

05-05 16:12:07.147: V/RefreshAPICall(1972): Priority set on refresh call is HIGH
05-05 16:12:07.147: V/RefreshAPICall(1972): Tag for Call is retry
05-05 16:12:07.265: E/Volley(1972): [137] BasicNetwork.performRequest: Unexpected response code **401 for https://url.me/api/get_friends**
05-05 16:12:07.265: V/TokenRetryPolicy(1972): Initiating a retry
05-05 16:12:07.265: V/TokenRetryPolicy(1972): AuthFailureError found!
05-05 16:12:07.265: V/VolleyUser(1972): refresh token task called
05-05 16:12:07.265: V/RefreshAPICall(1972): RefreshAPICall made with user user_password

05-05 16:12:07.265: V/RefreshAPICall(1972): Priority set on refresh call is HIGH
05-05 16:12:07.265: V/RefreshAPICall(1972): Tag for Call is retry
05-05 16:12:07.265: V/TokenRetryPolicy(1972): No attempt remaining,ERROR

05-05 16:12:08.219: I/Choreographer(1972): Skipped 324 frames!  The application may be doing too much work on its main thread.
05-05 16:12:08.230: V/RefreshAPICall(1972): Response from server on refresh is: {"status":"success","token":"d5792e18c0e1acb3ad507dbae854eb2cdc5962a2c1b610a6b77e3bc3033c7f64"}
05-05 16:12:08.230: V/VolleyUser(1972): Token from return is: d5792e18c0e1acb3ad507dbae854eb2cdc5962a2c1b610a6b77e3bc3033c7f64
05-05 16:12:08.231: V/TokenRetryPolicy(1972): Cancelled all retry calls
05-05 16:12:08.257: V/SessionManager(1972): New Token In SharedPref is: d5792e18c0e1acb3ad507dbae854eb2cdc5962a2c1b610a6b77e3bc3033c7f64
05-05 16:12:08.257: V/TokenRetryPolicy(1972): Logged new token

解决方法

现在我发现了一个答案,我找到了一种在重试时处理令牌刷新的方法.

当我使用Volley创建我的一般(最常见)API调用时,如果失败则保存对该调用的引用,并将其传递给我的重试策略.

public GeneralAPICall(int method,errorListener);
    sessionManager = SessionManager.getmInstance(context);
    HashMap<String,String> credentials = sessionManager.getUserDetails(); // Get the user's credentials for authentication
    this.listener = responseListener;
    this.params = params;
    // Encode the user's username and token
    String loginEncoded = new String(Base64.encode((credentials.get(Constants.SessionManagerConstants.KEY_USERNAME)
            + Constants.APIConstants.Characters.CHAR_COLON
            + credentials.get(Constants.SessionManagerConstants.KEY_TOKEN)).getBytes(),Constants.APIConstants.BasicAuth.BASIC + loginEncoded); // Set the encoded information as the header

    setRetryPolicy(new TokenRetryPolicy(context,this)); //passing "this" saves the reference
}

然后,在我的重试策略类(它只是扩展DefaultRetryPolicy,当我收到401错误告诉我需要一个新令牌时,我拍摄了一个refreshToken调用获取一个新令牌.

public class TokenRetryPolicy extends DefaultRetryPolicy implements IRefreshTokenReturn{
...

@Override
public void retry(VolleyError error) throws VolleyError {
    mCurrentRetryCount++; //increment our retry count
    mCurrentTimeoutMs += (mCurrentTimeoutMs * mBackoffMultiplier);
    if (error instanceof AuthFailureError && sessionManager.isLoggedIn()) {
        mCurrentRetryCount = mMaxnumRetries + 1; // Don't retry anymore,it's pointless
        VolleyUser.refreshTokenTask(context,this); // Get new token
    } if (!hasAttemptRemaining()) {
        Log.v(LOG_TAG,ERROR");
        throw error;
    }
}
...

}

一旦该调用返回,我在我的重试策略类中处理响应.我修改失败的调用,给它新的令牌(在将标记存储在SharedPrefs中之后)进行身份验证,然后再将其激活!

@Override
public void onTokenRefreshComplete(String token,String expiration) {
    sessionManager.setStoredToken(token,expiration);

    HashMap<String,String> credentials = sessionManager.getUserDetails(); //get the user's credentials for authentication

    //encode the user's username and token
    String loginEncoded = new String(Base64.encode((credentials.get(Constants.SessionManagerConstants.KEY_USERNAME)
            + Constants.APIConstants.Characters.CHAR_COLON
            + credentials.get(Constants.SessionManagerConstants.KEY_TOKEN)).getBytes(),loginEncoded); //Todo: remove
    callThatFailed.setHeaders(Constants.APIConstants.BasicAuth.AUTHORIZATION,Constants.APIConstants.BasicAuth.BASIC + loginEncoded); //modify "old,Failed" call - set the encoded information as the header

    VolleySingleton.getInstance(context).getRequestQueue().add(callThatFailed);
    Log.v(LOG_TAG,"fired off new call");
}

这个实现对我很有用.

但是,我应该注意这种情况不应该发生太多,因为我知道在进行任何API调用之前我应该​​检查我的令牌是否已过期.这可以通过在SharedPrefs中存储到期时间(从服务器返回),并查看current_time – 到期时间< some_time,some_time是你想要在它到期之前得到一个新令牌的时间,对我来说是10秒. 希望这可以帮助那里的人,如果我错了什么,请评论

相关文章

这篇“android轻量级无侵入式管理数据库自动升级组件怎么实现...
今天小编给大家分享一下Android实现自定义圆形进度条的常用方...
这篇文章主要讲解了“Android如何解决字符对齐问题”,文中的...
这篇文章主要介绍“Android岛屿数量算法怎么使用”的相关知识...
本篇内容主要讲解“Android如何开发MQTT协议的模型及通信”,...
本文小编为大家详细介绍“Android数据压缩的方法是什么”,内...