Android登录 - 帐户身份验证器与手动身份验证

时间:2015-08-10 09:09:13

标签: android authentication login accountmanager

我即将在我的应用中实施登录和用户身份验证。

我的第一个想法是手动完成,向服务器注册用户名和密码,获取身份验证令牌,保存并在后续请求中使用它。

在谷歌搜索后,我收集到了在Android上正确使用帐户身份验证器的方法。我已经看到了一些实施例子,但我不明白这样做的好处吗?是因为我可以存储多个帐户吗?是因为同步问题吗?如果有人能向我解释,我会很感激。它可能会让我更好地理解它的代码以及它为什么会这样做。

3 个答案:

答案 0 :(得分:97)

  

我可以存储多个帐户吗?

是。了解 Google Facebook 如何做到这一点。

  

是因为同步问题吗?

是的,您需要帐户才能使用SyncAdapter

等同步机制

为什么要使用AccountAuthenticator

  • 支持SyncAdapter;

  • 等后台同步机制
  • 验证用户身份的标准方法;

  • 支持不同的令牌;

  • 具有不同权限的帐户分享

你需要做什么?

1)。创建Authenticator;

2)。为用户登录创建Activity;

3)。创建Service以与帐户进行通信。

条款。

AccountManager - 它管理设备上的帐户。请求使用AccountManager

的身份验证令牌

AbstractAccountAuthenticator - 用于处理帐户类型的组件。它包含处理帐户的所有逻辑(授权,访问权限等)。不同的应用程序可能会使用一个AbstractAccountAuthenticator(例如Gmail帐户,日历,云端硬盘等)。

AccountAuthenticatorActivity - 基础Activity,用于授权/创建帐户。 AccountManager如果需要识别帐户(令牌不存在或已过期),则会调用此帐户

它是如何工作的?看下面的图片:

android account management diagram

的步骤。

1)。创建Authenticator;

您需要扩展AbstractAccountAuthenticator并覆盖7种方法:

  • Bundle editProperties(AccountAuthenticatorResponse response, String accountType) link
  • Bundle addAccount(AccountAuthenticatorResponse response, String accountType, String authTokenType, String[] requiredFeatures, Bundle options) link
  • Bundle confirmCredentials(AccountAuthenticatorResponse response, Account account, Bundle options) link
  • Bundle getAuthToken(AccountAuthenticatorResponse response, Account account, String authTokenType, Bundle options) link
  • String getAuthTokenLabel(String authTokenType) link
  • Bundle updateCredentials(AccountAuthenticatorResponse response, Account account, String authTokenType, Bundle options) link
  • Bundle hasFeatures(AccountAuthenticatorResponse response, Account account, String[] features) link

示例:

public class LodossAuthenticator extends AbstractAccountAuthenticator {

    private static final String LOG_TAG = LodossAuthenticator.class.getSimpleName();

    private final Context mContext;

    public LodossAuthenticator(Context context) {
        super(context);
        mContext = context;
    }

    @Override
    public Bundle editProperties(AccountAuthenticatorResponse response, String accountType) {
        return null;
    }

    @Override
    public Bundle addAccount(AccountAuthenticatorResponse response, String accountType, String authTokenType, String[] requiredFeatures, Bundle options) throws NetworkErrorException {
        final Intent intent = new Intent(mContext, CustomServerAuthenticatorSigninActivity.class);
        intent.putExtra(Config.ARG_ACCOUNT_TYPE, accountType);
        intent.putExtra(Config.ARG_AUTH_TYPE, authTokenType);
        intent.putExtra(Config.ARG_IS_ADDING_NEW_ACCOUNT, true);
        intent.putExtra(AccountManager.KEY_ACCOUNT_AUTHENTICATOR_RESPONSE, response);

        final Bundle bundle = new Bundle();
    bundle.putParcelable(AccountManager.KEY_INTENT, intent);
    return bundle;
    }

    @Override
    public Bundle confirmCredentials(AccountAuthenticatorResponse response, Account account, Bundle options) throws NetworkErrorException {
        return null;
    }

    @Override
    public Bundle getAuthToken(AccountAuthenticatorResponse response, Account account, String authTokenType, Bundle options) throws NetworkErrorException {
        // If the caller requested an authToken type we don't support, then
        // return an error
        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;
        }

        // Extract the username and password from the Account Manager, and ask
        // the server for an appropriate AuthToken.
        final AccountManager am = AccountManager.get(mContext);
        String authToken = am.peekAuthToken(account, authTokenType);

        // Lets give another try to authenticate the user
        if (TextUtils.isEmpty(authToken)) {
            final String password = am.getPassword(account);
            if (password != null) {
                try {
                    authToken = sServerAuthenticate.userSignIn(account.name, password, authTokenType);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }

        // If we get an authToken - we return it
        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;
        }

        // If we get here, then we couldn't access the user's password - so we
        // need to re-prompt them for their credentials. We do that by creating
        // an intent to display our AuthenticatorActivity.
        final Intent intent = new Intent(mContext, AuthenticatorActivity.class);
        intent.putExtra(AccountManager.KEY_ACCOUNT_AUTHENTICATOR_RESPONSE, response);
        intent.putExtra(com.lodoss.authlib.Config.ARG_ACCOUNT_TYPE, account.type);
        intent.putExtra(com.lodoss.authlib.Config.ARG_AUTH_TYPE, authTokenType);
        intent.putExtra(Config.ARG_ACCOUNT_NAME, account.name);
        final Bundle bundle = new Bundle();
    bundle.putParcelable(AccountManager.KEY_INTENT, intent);
    return bundle;
    }

    @Override
    public String getAuthTokenLabel(String authTokenType) {
        if (AccountGeneral.AUTHTOKEN_TYPE_FULL_ACCESS.equals(authTokenType))
            return AccountGeneral.AUTHTOKEN_TYPE_FULL_ACCESS_LABEL;
        else if (AccountGeneral.AUTHTOKEN_TYPE_READ_ONLY.equals(authTokenType))
            return AccountGeneral.AUTHTOKEN_TYPE_READ_ONLY_LABEL;
        else
            return authTokenType + " (Label)";
    }

    @Override
    public Bundle updateCredentials(AccountAuthenticatorResponse response, Account account, String authTokenType, Bundle options) throws NetworkErrorException {
        return null;
    }

    @Override
    public Bundle hasFeatures(AccountAuthenticatorResponse response, Account account, String[] features) throws NetworkErrorException {
        final Bundle result = new Bundle();
        result.putBoolean(KEY_BOOLEAN_RESULT, false);
        return result;
    }
}

说明:

因此,您只需要查看两种方法:addAccountgetAuthToken

addAccount我添加了一些配置参数,我的Activity将用于用户登录。这里的要点是intent.putExtra(Config.ARG_ACCOUNT_TYPE, accountType); - 您应该在此处指定帐户类型。其他操作不是必需的。

getAuthToken - 请阅读评论。我已从UdinicAuthenticator.java

复制粘贴此方法

此外,您需要在AndroidManifest.xml中使用以下权限:

<uses-permission android:name="android.permission.GET_ACCOUNTS" />
<uses-permission android:name="android.permission.MANAGE_ACCOUNTS" />
<uses-permission android:name="android.permission.AUTHENTICATE_ACCOUNTS" />
<uses-permission android:name="android.permission.USE_CREDENTIALS" />

方法addAccountgetAuthToken

的摘要

尝试获取令牌,如果令牌存在,则返回结果,否则您将看到Activity进行授权

2)。为用户登录创建Activity;

请参阅AuthenticatorActivity

简要说明: 使用UserId和密码创建表单。使用UserId&amp;密码数据从服务器获取身份验证令牌,然后执行以下步骤:

mAccountManager.addAccountExplicitly(account, accountPassword, null);
mAccountManager.setAuthToken(account, authtokenType, authtoken);

3)。创建Service以与帐户进行通信。

请参阅UdinicAuthenticatorService

不要忘记将此行添加到AndroidManifest.xmlService

    <intent-filter>
        <action android:name="android.accounts.AccountAuthenticator" />
    </intent-filter>
    <meta-data android:name="android.accounts.AccountAuthenticator"
               android:resource="@xml/authenticator" />

还在res/xml添加文件authenticator.xml

<?xml version="1.0" encoding="utf-8"?>
<account-authenticator xmlns:android="http://schemas.android.com/apk/res/android"
                       android:accountType="com.mediamanagment.app"
                       android:icon="@drawable/ic_launcher"
                       android:smallIcon="@drawable/ic_launcher"
                       android:label="@string/authenticator_label"/>

这就是全部。您可以使用AccountAuthenticator

感谢源材料

答案 1 :(得分:7)

AccountManager因以下原因而有用:

  • 首先,在单个帐户类型下存储对应用功能具有不同访问级别的多个帐户名称。例如,在视频流应用中,可能有两个帐户名:一个帐户名称可以访问有限数量的视频,另一个帐户名称可以全月访问所有视频。这不是使用Accounts的主要原因,但是,因为您可以在应用中轻松管理它,而无需使用这种看似奇特的Accounts ...
  • 使用Accounts的另一个好处是,每次用户请求授权功能时,都会使用用户名和密码取消传统授权,因为身份验证是在后台进行的,并且要求用户他们的密码仅在某些条件下,我稍后会知道。
  • 在android中使用Accounts功能也无需定义自己的帐户类型。您可能会遇到使用Google帐户进行授权的应用,这样可以省去制作新帐户和记住用户凭据的麻烦。
  • Accounts可以通过设置→帐户
  • 单独添加
  • 可以使用Accounts轻松管理跨平台用户授权。例如,客户端可以在他们的Android设备和PC中同时访问受保护的材料,而无需重复登录。
  • 从安全角度来看,在对服务器的每个请求中使用相同的密码允许在非安全连接中进行可能的窃听。此处密码加密不足以防止密码被盗。
  • 最后,在android中使用Accounts功能的一个重要原因是将依赖于Accounts的所有业务中涉及的双方分开,即所谓的身份验证者和资源所有者,而不会损害客户端(用户) )的证书。这些条款似乎相当含糊,但在阅读以下段落之前不要放弃......

让我详细说明后者的视频流应用程序示例。 A公司是与B公司签订合同的视频流业务的持有者,为其某些成员提供优质流媒体服务。公司B使用用户名和密码方法来识别其用户。对于公司A来认可B的高级会员,一种方法是从B获取它们的列表并使用类似的用户名/密码匹配机制。这样,验证者和资源所有者是相同的(公司A)。除了用户有义务记住第二个密码之外,很可能他们设置了与公司B的个人资料相同的密码来使用来自A的服务。这显然是不利的。

为了消除上述缺点,引入了OAuth。作为授权的开放标准,在上面的示例中,OAuth要求授权由公司B(身份验证者)通过为符合条件的用户(第三方)发布一些称为访问令牌的令牌,然后向公司A(资源所有者)提供令牌。所以没有令牌意味着没有资格。

我在here

的网站AccountManager上详细阐述了这一点及更多内容

答案 2 :(得分:0)

在Android的设置中,您拥有帐户类型的帐户,然后您可以添加帐户。 AccountManager也是存储凭证的中心位置,因此您只需为每个供应商登录一次。如果您下载其他Google应用或多次访问应用,则只需输入一次凭据