如果帐户身份验证器中的身份验证令牌已过期,则使用刷新令牌

Use refresh token if auth token expired in account authenticator

我有一个使用 AccountManager 来存储用户帐户的应用程序。用户使用 OAuth2.0 密码-用户名凭据流程通过我的 REST API 登录和注册。

用户收到的访问令牌将在2小时后过期,需要刷新直到再次过期,依此类推。

我需要在我的身份验证器中实现这个刷新功能。

我有一个名为 AccessToken 的模型,它具有以下字段:

String accessToken, String tokenType, Long expiresIn, String refreshToken, String scope, Long createdAt.

所以目前,在 AccountAuthenticator class 的 getAuthToken 方法中,我收到了这个 AccessToken 对象,并将其 accessToken 字段用作 Auth Token 我的客户经理。

我需要的是使用我的客户经理以某种方式存储刷新令牌和授权令牌,以及当应用程序尝试访问 API 并获得错误响应时:{"error": "access token expired"},以刷新当前访问令牌使用先前收到的 AccessToken 对象中的 refreshToken 字符串。但是,我不确定我应该怎么做。

我在验证器 class 中的 getAuthToken 方法目前看起来像这样:

@Override
public Bundle getAuthToken(AccountAuthenticatorResponse accountAuthenticatorResponse, Account account,
                           String authTokenType, Bundle options) throws NetworkErrorException {
    if (!authTokenType.equals(AccountGeneral.AUTHTOKEN_TYPE_READ_ONLY) &&
            !authTokenType.equals(AccountGeneral.AUTHTOKEN_TYPE_FULL_ACCESS)) {
        final Bundle result = new Bundle();
        result.putString(AccountManager.KEY_ERROR_MESSAGE, "Invalid authTokenType");
        return result;
    }

    final AccountManager manager = AccountManager.get(context.getApplicationContext());

    String authToken = manager.peekAuthToken(account, authTokenType);

    Log.d("Discounty", TAG + " > peekAuthToken returned - " + authToken);

    if (TextUtils.isEmpty(authToken)) {
        final String password = manager.getPassword(account);
        if (password != null) {
            try {
                authToken = discountyService.getAccessToken(DiscountyService.ACCESS_GRANT_TYPE,
                        account.name, password).toBlocking().first().getAccessToken();
// =======
// Here the above discountyService.getAccessToken(...) call returns
// AccessToken object on which I call the .getAccessToken() 
// getter which returns a string.
// =======
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    if (!TextUtils.isEmpty(authToken)) {
        final Bundle result = new Bundle();
        result.putString(AccountManager.KEY_ACCOUNT_NAME, account.name);
        result.putString(AccountManager.KEY_ACCOUNT_TYPE, account.type);
        result.putString(AccountManager.KEY_AUTHTOKEN, authToken);
        return result;
    }

    final Intent intent = new Intent(context, LoginActivity.class);
    intent.putExtra(AccountManager.KEY_ACCOUNT_AUTHENTICATOR_RESPONSE, accountAuthenticatorResponse);
    intent.putExtra(LoginActivity.ARG_ACCOUNT_TYPE, account.type);
    intent.putExtra(LoginActivity.ARG_AUTH_TYPE, authTokenType);
    final Bundle bundle = new Bundle();
    bundle.putParcelable(AccountManager.KEY_INTENT, intent);
    return bundle;
}

而 class AccountGeneral 只包含一些常量:

public class AccountGeneral {

    public static final String ACCOUNT_TYPE = "com.discounty";

    public static final String ACCOUNT_NAME = "Discounty";

    public static final String AUTHTOKEN_TYPE_READ_ONLY = "Read only";

    public static final String AUTHTOKE_TYPE_READ_ONLY_LABEL = "Read only access to a Discounty account";

    public static final String AUTHTOKEN_TYPE_FULL_ACCESS = "Full access";

    public static final String AUTHTOKEN_TYPE_FULL_ACCESS_LABEL = "Full access to a Discounty account";
}

该应用程序还将使用 SyncAdapter 并会非常频繁地与 API 交互以与服务器同步数据,并且这些 API 调用也需要使用访问令牌作为请求中的参数,所以我真的需要实现这个刷新功能并使其自动。


有人知道如何正确实施吗?


PS:我将使用本地数据库来存储我的所有数据,我也可以存储令牌对象。这似乎是一个简单的黑客攻击,虽然不安全。也许我应该每次只存储一个刷新令牌作为数据库记录,并在应用程序收到新令牌时更新它?

PPS:无论如何我都可以随意更改 API 的工作方式,所以如果有通过改进 API 来改进移动应用程序的建议,他们也非常感谢。

您可以在首次添加帐户时使用以下方法将刷新令牌保存到帐户的用户数据中:

Bundle userdata = new Bundle;
userdata.putString("refreshToken", refreshToken);
mAccountManager.addAccountExplicitly (account, password, userdata);

您也可以在添加账号后设置userdata,方法是调用:

mAccountManager.setUserData(account, "refreshToken", refreshToken);

当您的访问令牌过期时,您可以通过调用以下方式检索您的刷新令牌:

String refreshToken = mAccountManager.getUserData(account, "refreshToken");

使用 refreshToken 检索新的访问令牌。