如何在Android应用程序中使用JWT实现刷新令牌流程

6
我正在开发一个OAuth2令牌系统,用于访问我的REST API,并在Android应用程序中使用。我在客户端的令牌刷新部分遇到了一些问题。
流程如下:我的应用程序通过一些异步任务(PostCommentAsyncTask()AddFriendAsyncTask()等)向服务器发送请求(带有访问令牌参数)。如果accessToken有效,那就没问题,但是如果过期了,我会从之前的AsyncTaskonPostExecute()方法中调用另一个AsyncTask (GetRefreshTokenAsyncTask())来获取新的accessToken。这对我来说是棘手的部分。当我获得新的访问令牌时,我想重新执行最初的异步任务请求到服务器。我无法找出如何正确地执行它。
示例1:
请求PostCommentAsyncTask()-->(acessToken过期)-->GetRefreshTokenAsyncTask()-->请求PostCommentAsyncTask()-->(好的token)-->好的
编辑:
我最终选择使用Volley库(不再需要使用Asynctask)。 由于我使用JSON Web Token,我可以检查令牌负载中编码的到期日期。
以下是isAccessTokenExpired()方法,用于在向服务器发送请求之前检查Access Token是否已过期:
public Boolean isAccessTokenExpired(String accessToken){

        String[] accessTokenPart = accessToken.split("\\.");
        String header =accessTokenPart[0];
        String payload =accessTokenPart[1];
        String signature =accessTokenPart[2];

        try {

            byte[] decodedPayload = Base64.decode(payload, Base64.DEFAULT);
            payload = new String(decodedPayload,"UTF-8");
        } catch(UnsupportedEncodingException e) {
            e.printStackTrace();
        }


        try {
            JSONObject obj = new JSONObject(payload);
            int expireDate = obj.getInt("exp");
            Timestamp timestampExpireDate= new Timestamp( expireDate);
            long time = System.currentTimeMillis();
            Timestamp timestamp = new Timestamp(time);

            return  timestamp.after(timestampExpireDate);

        } catch (JSONException e) {
            e.printStackTrace();
            return true;
        }

    }

这里有一个refreshJsonWebToken()方法,可以从我的OAUTH2服务器获取新的访问令牌/刷新令牌:

public void refreshJsonWebToken(){


            SharedPreferences settings = getActivity().getSharedPreferences(PREFS_NAME, 0);
            String refreshToken = settings.getString("refreshToken", null);

            final HashMap<String, String> params = new HashMap<String, String>();
            params.put("grant_type","refresh_token");
            params.put("client_id","client");
            params.put("refresh_token",refreshToken);

            JsonObjectRequest req = new JsonObjectRequest(URL_OAUTH2, new JSONObject(params), new Response.Listener<JSONObject>() {

                @Override
                public void onResponse(JSONObject response) {

                        try {

                            String newRefreshToken = response.getString("refresh_token");
                            SharedPreferences settings = getActivity().getSharedPreferences(PREFS_NAME, 0);
                            SharedPreferences.Editor editor = settings.edit();
                            editor.putString("accessToken", newAccessToken);
                            editor.putString("refreshToken", newRefreshToken);
                            editor.apply();
                        } catch (JSONException e) {
                            e.printStackTrace();
                        }
                    }
            }, new Response.ErrorListener() {


                @Override
                public void onErrorResponse(VolleyError error) {
                    Log.e("grid", "Error: " + error.getMessage());

                    }
                }
            });

            AppController.getInstance().addToRequestQueue(req);


    }

最后是 getPost() 方法,其中我使用了前面的方法:

private void getPost(String latitude, String longitude) {

        SharedPreferences settings = getActivity().getSharedPreferences(PREFS_NAME, 0);
        String accessToken = settings.getString("accessToken", null);
        final HashMap<String, String> params = new HashMap<String, String>();
        params.put("action", "getLocalPosts");
        params.put("latitude", latitude);
        params.put("longitude", longitude);

        if (isAccessTokenExpired(accessToken)){
            refreshJsonWebToken();
        }

        settings = getActivity().getSharedPreferences(PREFS_NAME, 0);
        accessToken = settings.getString("accessToken", null);
        JsonObjectRequest req = new JsonObjectRequest(URL_APP+accessToken, new JSONObject(params), new Response.Listener<JSONObject>() {

            //Some code ....
        });

        AppController.getInstance().addToRequestQueue(req);

    }

你可以在第一个AsyncTask的doInBackground()方法中使用第二个AsyncTask的get()方法。 AsyncTask的get()方法是一种阻塞方法,它返回一个结果 - 在主/UI线程上使用它完全没有用处,但如果你从第一个AsyncTask的doInBackground()方法(在其自己的线程上运行)中调用它,那么一旦第二个AsyncTask的get()方法返回,你就可以简单地继续原始任务。这只是一个想法。 - Squonk
@Frédéric:你在服务器端使用了哪个库来进行OAuth2密码授权?我正在使用Node作为后端。 - j10
1个回答

2
我认为在这种情况下,Handler 更好,因为 Looper 拥有同步消息队列,这在这里非常方便。您可以创建一个 HandlerThread 并将您的 Handler 与之关联。然后,您可以根据需要调用 postRunnable,例如,如果令牌已过期,则添加 GetRefreshTokenRunnablePostCommentRunnable,它们将按顺序执行。
如果您仍然想使用 AsyncTasks,那么在启动 PostCommentAsyncTask 之前,可以检查令牌是否已过期吗?我认为这将是更好的设计。如果您无法这样做,则可以按顺序执行它们,因为它们默认在相同的后台线程上工作,例如:
new PostCommentAsyncTask().execute();

class PostCommentAsyncTask extends AsyncTask {
    //...
    onPostExecute() {
        if (tokenExpired) {
            new GetRefreshTokenAsyncTask().execute();
            new PostCommentAsyncTask().execute(); // this guy will wait till refresh token is executed.
        }
    }
}

1
谢谢你的回答!我确实可以在发出请求之前检查令牌(感谢JWT)。我是Android的新手,对Handler不太了解,也不太清楚Volley的处理方式,但它有一个队列系统作为Handler。我编辑了我的帖子,并希望它能得到改进或帮助任何人! - Frédéric
好的,谢谢您更新帖子。那么,您现在有什么问题吗?我认为现在一切都应该很清楚了。 - Gennadii Saprykin

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