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

Dan*_*lio 37 authentication android login accountmanager

我即将在我的应用程序中实现登录以及用户身份验证.

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

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

Ale*_*der 103

我可以存储多个帐户吗?

是.了解GoogleFacebook如何 做到这一点.

是因为同步问题吗?

是的,您需要帐户才能使用同步机制 SyncAdapter

你为什么要用AccountAuthenticator

  • 支持后台同步机制如SyncAdapter;

  • 验证用户的标准方法;

  • 支持不同的令牌;

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

你需要做什么?

1).创造Authenticator;

2).创建Activity用户登录;

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

条款.

AccountManager - 它管理设备上的帐户.请求您应该使用的auth令牌AccountManager.

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

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

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

android帐户管理图

脚步.

1).创造Authenticator;

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

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

例:

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;
    }
}
Run Code Online (Sandbox Code Playgroud)

说明:

所以,你只需要看到两种方法:addAccount, getAuthToken.

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" />
Run Code Online (Sandbox Code Playgroud)

方法addAccount和方法摘要getAuthToken

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

2).创建Activity用户登录;

请参见AuthenticatorActivity

简要说明: 使用UserId和Password创建表单.使用UserId和Password数据从服务器获取auth令牌,然后执行以下步骤:

mAccountManager.addAccountExplicitly(account, accountPassword, null);
mAccountManager.setAuthToken(account, authtokenType, authtoken);
Run Code Online (Sandbox Code Playgroud)

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" />
Run Code Online (Sandbox Code Playgroud)

并且还在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"/>
Run Code Online (Sandbox Code Playgroud)

就这样.你可以使用你的AccountAuthenticator.

对于源材料感谢

  • 很好的答案.您不仅解释了为什么应该使用帐户身份验证器,而且您还要加倍努力并解释其实施以及为什么需要执行每个步骤. (2认同)

Ali*_*Nem 7

AccountManager有好处,原因如下:

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

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

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

我已经阐述了更多关于这和更多的AccountManager在我的网站在这里