确保有效的身份验证令牌始终可用

Ensuring a valid auth token is always available

我一直在想办法为我的 android 应用程序验证用户身份。它基于一个已经开发的网站api,使用JWT 进行身份验证。

我遇到了刷新令牌的问题。假设我想从 API 中获取一些东西,并且我需要为此提供授权令牌。我检查我当前的身份验证令牌。如果它已过期,我需要使用某种刷新令牌获取一个新的。

然而,似乎几乎不管我怎么想去尝试实现它,我运行遇到了几个问题:

  1. 我不希望 UI 线程在我获得新令牌时等待
  2. 我宁愿不必显式检查令牌是否 在进行任何 API 调用
  3. 之前是否存在(然后刷新它)

我提出了一种解决方案,它可以解决 #1,并且至少可以最大限度地减少 #2 的痛苦。我可以有某种 getToken 方法。例如,使用 JS 风格的 promises 因为它们对我来说更容易理解:

function getToken() {
  return new Promise((resolve) => {
    // Check for token, and return if valid.
    // Otherwise, go to the server and get a new one
    ...
    resolve(token)
  }
}

// When making an API call
getToken().then((token) => {
  // Call API
})

我想我可以解决这个问题,这样请求就永远不会在 UI 线程上 运行ning,这解决了 #1,就 #2 而言,它至少是可以忍受的.

我的问题是:有更好的方法吗?看起来 AccountManager 可能能够为我处理这类事情,但它的文档充其量是低于标准的,所以我不确定我将如何实现它。如果 AccountManager 可以做到并且您知道一个很好的教程,请评论。

实现此目的的一种方法是拦截 401 状态代码和刷新令牌。

如果您正在使用 Volley,您可以扩展 Request class 并覆盖 parseNetworkEror(VolleyError error) 方法。如果需要,安排一个将刷新令牌的作业 (JobDispatcher) and trigger an event to communicate UI about the change (EventBus)。

以下示例使用 OAuth 身份验证,但可以轻松更改为实现 JWT。

@Override
protected VolleyError parseNetworkError(VolleyError volleyError) {

    if (getDataAccess().shouldRefreshToken(volleyError)) {

        if (!EventBus.getDefault().hasSubscriberForEvent(TokenRefreshedEvent.class)) {
            EventBus.getDefault().register(this);
        }

        CSApplication app = CSApplication.getInstance();
        FirebaseJobDispatcher dispatcher = app.getJobDispatcher(app.getApplicationContext());
        Job myJob = dispatcher.newJobBuilder()
                .setService(JobRefreshToken.class)
                .setTag("REFRESH_TOKEN")
                .setTrigger(Trigger.NOW)
                .setRetryStrategy(RetryStrategy.DEFAULT_EXPONENTIAL)
                .setConstraints(Constraint.ON_ANY_NETWORK)
                .build();

        int result = dispatcher.schedule(myJob);

        if (result == FirebaseJobDispatcher.SCHEDULE_RESULT_SUCCESS) {
            LogUtils.log(LogUtils.Type.JOB, GsonRequest.class, "Scheduling job refresh token");
        } else {
            LogUtils.log(LogUtils.Type.JOB, GsonRequest.class, "Error on schedule refresh token");
        }
    }

    return super.parseNetworkError(volleyError);
}

public boolean shouldRefreshToken(VolleyError error) {
    boolean shouldRefreshToken = error.networkResponse != null && error.networkResponse.statusCode == 401;

    if (shouldRefreshToken) {
        Map<String, String> headers = error.networkResponse.headers;

        if (headers.containsKey("WWW-Authenticate")) {
            String value = headers.get("WWW-Authenticate");

            boolean issuerInvalid = value.contains("The issuer is invalid");

            shouldRefreshToken = !issuerInvalid;

            if (issuerInvalid) {
                log(LogUtils.Type.VOLLEY, DataAccess.class, "Issuer do token é inválido");
            }
        }
    }

    return shouldRefreshToken;
}

工作代码

getDataAccess().refreshToken(getApplicationContext(), new VolleyCallback<Void>() {
        @Override
        public void onSuccess(Void aVoid) {
            EventBus.getDefault().post(new TokenRefreshedEvent(true));

            job.jobFinished(params, false);

            log(LogUtils.Type.JOB, JobRefreshToken.class, "Refresh Token job finished");
        }

        @Override
        public void onError(VolleyError error) {
            super.onError(error);

            EventBus.getDefault().post(new TokenRefreshedEvent(false));

            job.jobFinished(params, false);
        }
    });

    return true;
}

我最终做的是创建一个方法 getToken,它可以 returns 当前令牌或获得一个新令牌(阻塞)。使用此策略,我需要确保永远不会从 UI 线程调用它。我创建了一个调用 getTokenRetrofit2 拦截器。这种方法的好处是我可以调用我的 Retrofit 方法而不用担心令牌,它会检查是否过期并在必要时获取一个新的。