1. 程式人生 > >介紹一個很全面原始碼關於android 賬戶管理的原始碼

介紹一個很全面原始碼關於android 賬戶管理的原始碼

基礎開始——這些是主要部分:

認證令牌(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上面找到這個原始碼,還有部落格介紹,稍微用有道翻譯部分說明。