Quantcast
Channel: CSDN博客移动开发推荐文章
Viewing all articles
Browse latest Browse all 5930

介绍一个很全面源码关于android 账户管理的源码

$
0
0

基础开始——这些是主要部分:

认证令牌(auth- Token)-服务器提供的临时访问令牌(或安全令牌)。用户需要识别出这样的标记并将其附加到他发送到服务器的每个请求上。在这篇文章中,我将使用OAuth2作为认证标准,因为它是最流行的方法。

您的身份验证服务器——将管理使用您的产品的所有用户的服务器。它将为登录的任何用户生成一个authtoken,并为用户在您的服务器上所做的每一个请求验证它。auth- token可以是时间有限的,在一段时间后过期。

AccountManager——管理设备上的所有帐户,并运行该显示。应用程序可以从它请求auth- token,这是它的工作。无论它是否意味着它需要打开一个新的“登录”/“创建帐户”活动,或者检索先前请求的存储的auth- token,AccountManager知道要调用谁以及在每个场景中做什么来完成任务。

AccountAuthenticator——一个处理特定帐户类型的模块。AccountManager找到合适的AccountAuthenticator与它进行对话,以执行account类型上的所有操作。AccountAuthenticator知道向用户显示哪些活动来输入他的凭据,以及在哪里找到服务器已经返回的任何存储的auth- token。在单一帐户类型下,这可能是许多不同服务的共同之处。例如,谷歌在Android上的authenticator正在验证谷歌邮件服务(Gmail)以及谷歌日历和谷歌驱动器等其他谷歌服务。

AccountAuthenticatorActivity——基类被称为“登录/创建账户”活动的身份当用户需要确定自己。该活动负责对服务器的登录或帐户创建过程,并将auth- token返回给调用authenticator。

每当应用程序需要一个auth- token时,它只会使用一个方法,AccountManager # getAuthToken()。AccountManager会从那里取下来并通过hoops来得到这个标记。这里有一个从谷歌的文档中得到的一个很好的图表:



看起来有点麻烦,但其实很简单。我将解释在设备上第一次登录到帐户的常见情况。

第一次记录

应用程序向AccountManager请求一个auth- token。

AccountManager询问相关的AccountAuthenticator是否为我们提供了令牌。

因为它没有(没有登录的用户),它告诉我们一个AccountAuthenticatorActivity,将允许用户登录。

从服务器返回用户登录和auth- token。

auth- token存储在AccountManager中,供将来使用。

该应用程序得到它所请求的auth- token。

如果用户已经登录,我们将在第二步中获得auth- token。您可以在这里阅读更多关于使用OAuth2认证的信息。

现在,我们已经了解了基础知识,让我们看看如何创建我们自己的帐户类型验证器。

创建身份验证

如前所述,帐户验证器是由AccountManager解决的,以满足所有的相关任务:获取存储的auth- token,显示帐户登录屏幕,并处理服务器上的用户身份验证。

创建我们自己的身份需要延长AbstractAccountAuthenticator和实现一些方法。现在让我们专注于两种主要方法:

addAccount

当用户想登录时调用,并在设备中添加一个新帐户。

我们需要返回一个包的意图开始_AccountAuthenticatorActivity _(稍后解释)。这个方法可以通过调用AccountManager # addAccount()调用AccountManager # addAccount(),或者从手机的设置屏幕上调用。



@Override
public Bundle addAccount(AccountAuthenticatorResponse response, String accountType, String authTokenType, String[] requiredFeatures, Bundle options) throws NetworkErrorException {
    final Intent intent = new Intent(mContext, AuthenticatorActivity.class);
    intent.putExtra(AuthenticatorActivity.ARG_ACCOUNT_TYPE, accountType);
    intent.putExtra(AuthenticatorActivity.ARG_AUTH_TYPE, authTokenType);
    intent.putExtra(AuthenticatorActivity.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;
}

getAuthToken

由上图解释。获取在此设备上成功登录的帐户类型的存储auth- token。如果没有这样的东西,用户就会被提示登录。在成功登录后,请求应用程序将获得期待已久的authtoken。要做到这一点,我们需要检查AccountManager是否有一个可用的auth- token,使用AccountManager # peekAuthToken()。如果没有,我们返回的结果与addAccount()相同。

@Override
public Bundle getAuthToken(AccountAuthenticatorResponse response, Account account, String authTokenType, Bundle options) throws NetworkErrorException {

    // 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) {
            authToken = sServerAuthenticate.userSignIn(account.name, password, authTokenType);
        }
    }

    // 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(AuthenticatorActivity.ARG_ACCOUNT_TYPE, account.type);
    intent.putExtra(AuthenticatorActivity.ARG_AUTH_TYPE, authTokenType);
    final Bundle bundle = new Bundle();
    bundle.putParcelable(AccountManager.KEY_INTENT, intent);
    return bundle;
}

如果我们从这个方法得到的auth- token不再有效,因为时间过期或从不同客户端更改密码,您需要将当前的auth- token失效,并再次请求令牌。通过调用AccountManager # invalidateAuthToken()使当前标记失效。下一个调用getAuthToken()将尝试登录到存储的密码,如果失败,用户将不得不再次输入他的凭证。

所以. .用户将在何处输入他的凭据?会在我们AccountAuthenticatorActivity推导

创建活动

我们AccountAuthenticatorActivity是唯一直接与用户交互,我们有。

此活动将向用户显示登录表单,对其进行身份验证,并将结果返回给调用验证器。我们从AccountAuthenticatorActivity扩展的原因,并从常规的活动,不仅是setAccountAuthenticatorResult()方法。此方法负责从活动的身份验证过程中收回结果,并将其返回给身份验证器,后者首先调用该活动。它为我们省去了保持响应接口与身份验证器进行通信的需要。

我在我的活动上建立了一个简单的用户名/密码表。您可以使用Android站点上建议的登录活动模板。提交时,我称此方法为:

public void submit() {
    final String userName = ((TextView) findViewById(R.id.accountName)).getText().toString();
    final String userPass = ((TextView) findViewById(R.id.accountPassword)).getText().toString();
    new AsyncTask<Void, Void, Intent>() {
        @Override
        protected Intent doInBackground(Void... params) {
            String authtoken = sServerAuthenticate.userSignIn(userName, userPass, mAuthTokenType);
            final Intent res = new Intent();
            res.putExtra(AccountManager.KEY_ACCOUNT_NAME, userName);
            res.putExtra(AccountManager.KEY_ACCOUNT_TYPE, ACCOUNT_TYPE);
            res.putExtra(AccountManager.KEY_AUTHTOKEN, authtoken);
            res.putExtra(PARAM_USER_PASS, userPass);
            return res;
        }
        @Override
        protected void onPostExecute(Intent intent) {
            finishLogin(intent);
        }
    }.execute();
}

ServerAuthenticate是对我们的身份验证服务器的接口。我实现了一些方法,比如userSignIn和userSignUp,它将从服务器返回auth- token,成功登录。

mAuthTokenType是我从服务器请求的标记类型。我可以让服务器给我不同的令牌,以供只读或完全访问帐户,甚至在相同帐户内的不同服务。一个很好的例子是谷歌帐户,它提供了一些authtoken类型:“_Manage your日历”、“Manage your _tasks”、“查看日历”等等。在这个特殊的例子中,我不为各种auth- token类型做任何不同的事情。

完成后,我调用finishLogin():

private void finishLogin(Intent intent) {
    String accountName = intent.getStringExtra(AccountManager.KEY_ACCOUNT_NAME);
    String accountPassword = intent.getStringExtra(PARAM_USER_PASS);
    final Account account = new Account(accountName, intent.getStringExtra(AccountManager.KEY_ACCOUNT_TYPE));
    if (getIntent().getBooleanExtra(ARG_IS_ADDING_NEW_ACCOUNT, false)) {
        String authtoken = intent.getStringExtra(AccountManager.KEY_AUTHTOKEN);
        String authtokenType = mAuthTokenType;
        // Creating the account on the device and setting the auth token we got
        // (Not setting the auth token will cause another call to the server to authenticate the user)
        mAccountManager.addAccountExplicitly(account, accountPassword, null);
        mAccountManager.setAuthToken(account, authtokenType, authtoken);
    } else {
        mAccountManager.setPassword(account, accountPassword);
    }
    setAccountAuthenticatorResult(intent.getExtras());
    setResult(RESULT_OK, intent);
    finish();
}

该方法获得一个新的auth- token并执行以下操作:

在此情况下,现有帐户中有一个无效的auth- token,我们在AccountManager上已经有了一个记录。新的auth- token将取代旧的,没有您的任何操作,但是如果用户更改了他的密码,您也需要使用新的密码来更新AccountManager。这可以在上面的代码中看到。

你在设备上添加一个新帐户——这是一个棘手的部分。在创建帐户时,auth- token并不能立即保存到AccountManager,它需要显式保存。这就是为什么在将新帐户添加到AccountManager之后,我明确地设置了auth- token。如果不这样做,将会使AccountManager再次访问服务器,当调用getAuthToken方法时,并再次验证用户。

注意:对于addaccount显式()的第三个参数是一个“用户数据”包,它可以用来存储自定义数据,比如您的服务的API密钥,以及与AccountManager上的其他身份验证相关的数据。这也可以通过使用setUserData()来设置。

在此活动完成登录过程后,我们将帐户管理器设置为AccountManager。调用setAccountAuthenticatorResult()返回回身份验证的信息。

现在我们已经准备好了,但是谁来开始呢?它将如何获得访问权?我们需要让我们的认证器适用于所有想使用它的应用程序,包括Android设置屏幕。因为我们也希望它在后台运行(登录屏幕是可选的),使用服务是显而易见的选择。

创建服务

我们的服务将非常简单。

我们所要做的就是让其他进程与我们的服务绑定,并与我们的认证器进行通信。AbstractAccountAuthenticator幸运的是,我们的认证器扩展,getIBinder()方法,它返回一个实现内部。我们的服务需要在它的onBind()方法上调用它!基本实现需要通过外部流程的请求调用验证器上的适当方法。看到它实际上是如何工作的,你可以看一下交通,一个内部类AbstractAccountAuthenticator和阅读AIDL进程间通信。

我们的服务是这样的:

public class UdinicAuthenticatorService extends Service {
    @Override
    public IBinder onBind(Intent intent) {
        UdinicAuthenticator authenticator = new UdinicAuthenticator(this);
        return authenticator.getIBinder();
    }
}

需要在添加

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

我们将xml作为资源链接,用于为身份验证器定义一些属性。这就是它看起来:

<account-authenticator xmlns:android="http://schemas.android.com/apk/res/android"
                       android:accountType="com.udinic.auth_example"
                       android:icon="@drawable/ic_udinic"
                       android:smallIcon="@drawable/ic_udinic"
                       android:label="@string/label"
                       android:accountPreferences="@xml/prefs"/>

accountType是识别我们的帐户类型的惟一名称。当一些应用程序想要对我们进行身份验证时,它需要在接近AccountManager时使用这个名称。

图标和smallIcon是在设备的设置页面和帐户批准页面上看到的帐户的图标(稍后将详细介绍)。

标签是在设备设置页面上列出我们的帐户的字符串。

accountPreferences是对首选项XML的引用。当从设备设置屏幕访问帐户的首选项时,这将显示,允许用户对帐户进行更多的控制。你可以查看谷歌和Dropbox让你改变他们的帐户的一些例子。这是我自己的一个例子:

随便你可能想知道的东西

在我的调查过程中,我遇到了一些有趣的情况,我想我应该分享一下,在使用这个API的同时保持你的头发完整。

检查现有帐户的有效性——如果您想为您自己存储的帐户名称获得一个auth- token,请检查这个帐户是否仍然首先使用AccountManager # getAccounts *()方法。我将引用AccountManager的文档:

“请求在设备上不再使用一个auth令牌,从而导致未定义的失败。”

对我来说,“未定义的失败”就是把登录页面带来,然后在我提交了我的证书之后什么都不做,所以你就有了。

首先,首先是服务——假设你将你的身份验证者的代码复制到你的两个应用程序中,从而共享它的逻辑,并在每个应用程序上改变登录页面的设计,以适应它所属的应用程序。在这种情况下,第一个安装的应用程序的身份验证器将在请求auth- token时调用两个应用程序。如果你卸载第一个应用程序,第二个应用的认证器将从现在开始调用(因为它是现在唯一的一个)。解决这个问题的一个技巧是将两个登录页面放在同一个身份验证器上,然后在addAccount()方法上使用addAccountOptions参数来传递您的设计请求。

共享是关心. .对于安全性——如果您试图从一个不同应用程序创建的验证器中获得一个auth- token,该应用程序使用不同的签名键签名,用户必须显式地批准该操作。这是用户将看到的。

我编译例子的部分界面





找android 账户管理,在github上面找到这个源码,还有博客介绍,稍微用有道翻译部分说明。

源码地址:http://download.csdn.net/download/qq_16064871/9937005

github:https://github.com/Udinic/AccountAuthenticator

作者:qq_16064871 发表于2017/8/25 23:03:27 原文链接
阅读:296 评论:0 查看评论

Viewing all articles
Browse latest Browse all 5930

Trending Articles