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

时间:2017-08-10 00:47:44

标签: android authentication accountmanager

我一直在试图弄清楚如何为我的Android应用验证用户。它基于已经开发了api的网站,使用JWT进行身份验证。

我反对刷新令牌的问题。假设我想从API获取内容,我需要auth令牌。我检查我当前的身份验证令牌。如果它已过期,我需要使用某种刷新令牌来获取新的。

然而,似乎无论我怎么想实现它,我都遇到了一些问题:

  1. 我不希望UI线程在我获得新令牌时等待
  2. 我希望我不必明确检查是否有令牌 在进行任何API调用之前是否存在(然后刷新它)
  3. 我提出了一个解决#1的解决方案,至少可以减少#2的痛苦。我可以使用某种getToken方法。举个例子,使用JS风格的承诺,因为它让我更容易理解:

    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线程上运行,这解决了#1,而就#2而言,它至少是可以忍受的。

    我的问题是:有更好的方法吗?似乎AccountManager可能能够为我处理这类事情,但它的文档充其量只是次要的,所以我不确定如何实现它。如果AccountManager能够做到并且你知道一个很好的教程,请对此进行评论。

2 个答案:

答案 0 :(得分:1)

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

如果您使用的是Volley,则可以扩展Request类并覆盖parseNetworkEror(VolleyError error)方法。如果需要,请安排一个作业,该作业将刷新令牌(JobDispatcher)并触发事件以传达有关更改的UI(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;
}

答案 1 :(得分:0)

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