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

Posted

技术标签:

【中文标题】如果身份验证令牌在帐户身份验证器中过期,则使用刷新令牌【英文标题】:Use refresh token if auth token expired in account authenticator 【发布时间】: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 中找到基本网址 【参考方案1】:

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

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

添加账号后也可以通过调用来设置用户数据:

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

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

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

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

【讨论】:

请注意,您应该始终使用setUserData设置用户数据,因为已知addAccountExplicitly 已损坏。它并不总是设置捆绑包中传递的用户数据。见***.com/a/34601122/1558654 @Grace Coder 如何使用刷新令牌使用帐户管理器从服务器检索新的访问令牌。我如何使用帐户管理器使用刷新令牌管理访问令牌的到期。请回复... @KJEjava48 当您从服务器检索刷新令牌时,服务器将返回刷新令牌和刷新令牌的到期时间。然后在您的客户经理中,您需要在请求新的访问令牌之前通过比较设备返回的当前日期来检查刷新令牌是否已过期。如果刷新令牌过期,您需要重新完成 OAuth 2 的整个过程,包括再次提示用户输入他/她的密码。

以上是关于如果身份验证令牌在帐户身份验证器中过期,则使用刷新令牌的主要内容,如果未能解决你的问题,请参考以下文章

如何使用 Firebase 刷新令牌保持用户身份验证?

刷新令牌是不是过期,如果过期,何时过期?

jwt 访问令牌和刷新令牌流

谷歌帐户的不同身份验证令牌

SharePoint身份验证令牌 - 保存公式

使用 JWT 和刷新令牌对移动应用程序进行身份验证