【发布时间】:2016-04-04 08:46:55
【问题描述】:
我有一个使用 AccountManager 来存储用户帐户的应用。用户使用 OAuth2.0 密码-用户名凭据流程通过我的 REST API 登录和注册。
用户收到的访问令牌2小时后过期,需要刷新直到再次过期,以此类推。
我需要在我的身份验证器中实现这个刷新功能。
我有一个名为 AccessToken 的模型,它具有以下字段:
String accessToken, String tokenType, Long expiresIn, String refreshToken, String scope, Long createdAt.
因此,目前,在我的 AccountAuthenticator 类中的 getAuthToken 方法中,我接收到此 AccessToken 对象并将其 accessToken 字段用作我的客户经理的 Auth Token。
我需要的是使用我的帐户管理器以某种方式存储刷新令牌和身份验证令牌,并且当应用程序尝试访问 API 并获得错误响应:{"error": "access token expired"},以使用 refreshToken 刷新当前访问令牌来自先前收到的 AccessToken 对象的字符串。但是,我不确定我应该怎么做。
我在验证器类中的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;
}
而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 来改进移动应用程序的建议,我们也非常感谢。
【问题讨论】:
-
你是如何获得刷新令牌的?
-
@Denis Yakovenko 你能分享你的代码吗,因为我也很难理解如何使用账户管理器管理访问和刷新令牌以及同步功能。如果你可以分享一下
-
@KJEjava48 我不能说我找到了一个很好的解决方案并且链接上的代码很好,但仍然是here's my repo with the whole app。看看
app/src/main/java/discounty/com/activities/LoginActivity.java。刷新令牌的使用主要在那里。希望对你有帮助 -
@Denis Yakovenko 哦..谢谢你的这个例子。它似乎很大但仍然比我看到的所有其他例子更好地理解逻辑,但我仍然有疑问,在哪里是调用您的服务器 url 进行身份验证、令牌刷新等的部分。快速查看后,我没有看到调用您自己的服务器进行身份验证和令牌刷新的 url
-
@KJEjava48 您没有看到网址,因为我正在使用 Retrofit library 进行 API 调用。这是定义我所有 url 和查询的文件:interfaces/DiscountyService.java。您还可以在api/ServiceGenerator.java 中找到基本网址
标签: android api authentication access-token accountmanager