Write your own Android Authenticator
http://blog.udinic.com/2013/04/24/write-your-own-android-authenticator/
一、Android的账号体系基本原理
-
AccountManager用于管理各个类型的账号,可以通过它获取和添加账号信息
-
AccountAuthenticator一个用于处理特定类型账号的模块。AccountManager通过找到相应的类型的AccountAuthenticator
去进行该类型账号的一系列操作。AccountAuthenticator知道展示哪个Activity给用户进行认证登录,并知道从哪里找到之前存储的服务器返回的auth-token给用户。 -
AccountAuthenticatorActivity是登录和创建账号Activity的基类。当用户需要登录认证的时候被AccountAuthenticator调用展示给用户。该Activity负责处理和服务器进行的登录和创建账号过程,并在成功后将auth-token返回给调用的AccountAuthenticator。
-
为了让AccountAuthenticator可以被所有APP使用,同时也无需界面交互,我们需要创建Service。让其他进程绑定到我们的Service并和AccountAuthenticator进行交流。
—— ------------- ------------- ——
-
Authentication Token (auth-token) :服务器返回的临时访问token。用户通过登录认证获得它,并在和服务器以登录态进行交互时需要带着该token。
-
认证服务器,该服务器管理着所有的用户,它将会在用户登录的时候产生一个auth-token给用户;并在用户后续的登录态交互中对其(auth-token)进行验证。auth-token可以设置有效时间,当过了有效时间后便失效了。
二、调用和内部创建流程
- 外部调用
AccountManager.get(this).addAccount(MY_ACCOUNT_TYPE, null, null, null, null, new AccountManagerCallback<Bundle>() {
@Override
public void run(AccountManagerFuture<Bundle> future) {
try {
Bundle bundle = future.getResult();
if (bundle != null && bundle.containsKey(AccountManager.KEY_INTENT)) {
Intent intent = bundle.getParcelable(AccountManager.KEY_INTENT);
startActivity(intent);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}, null);
- 内部创建Authenticator
public class MyAuthenticator extends AbstractAccountAuthenticator {
// 当用户登录和创建一个新的账号的时候调用
@Overide
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;
}
// 获取该类型账号之前登录成功而存储的auth-token
@Override
public Bundle getAuthToken(AccountAuthenticatorResponse response, Account account, String authTokenType, Bundle options) throws NetWordErrorException {
// Extract the username and password from the Accounot Manager,
// and ask the server for an appropriate AuthToken.
final AccountManager am = AccountManager.get(mContext);
String authToken = am.peekAuthToken(account, authTokenType);
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 auth-token
// 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_RESPOONSE, response);
intent.putExtra(AuthticatorActivity.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;
}
}
- 创建登录注册Activity
// The reason we extend from AccountAuthenticatorActivity is
// the setAccountAuthenticatorResult()method. this method is
// in charge of taking back the result from the authentication process
// on the activity and return it to the Authticator, who called this activity
// in the first place.
public class LoginActivity extends AccountAuthenticatorActivity {
...
// when submitting I call this method
public void submit() {
final String userName = ((TextView)findViewById(R.id.name)).getText().toString();
final String userPass = ((TextView)findViewById(R.id.pass)).getText().toString();
new AsyncTask<Void, Void, Intent>() {
@Override
protected Intent doInBackground(Void...params) {
// sServerAuthenticate is the interface to our authenticating server.
// mAuthTokenType is the type ot token that I request form the server. I can have the server give me different token for read-only or full access to an account, or even for different sevices within the same account. A good example is the Google account, which provides several auth-token types:"_Manage your calendars", "Manage your _tasks", "View your calendars" and more..
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, ACOUNT_TYPE);
res.putExtra(AccountManager.KEY_AUTHTOKEN, authToken);
return res;
}
@Override
protected void onPostExecute(Intent intent) {
finishLogin(intent);
}
}.execute();
}
private void finishLogin(Intent intent) {
String accountName = intent.getStringExtra(AccountManager.KEY_ACCOUNT_NAME);
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
// if not setting the auth token will cause another call to the server to authenticate the user
mAccountManager.setAuthToken(account, authTokenType, authToken);
}
setAccountAuthenticatorResult(intent.getExtras());
setResult(RESULT_OK, intent);
finish();
}
}
- 创建Service
public class MyAuthenticatorService extends Service {
@Overrice
public IBinder onBind(Intent intent) {
MyAuthenticator authenticator = new MyAuthenticator(this);
return authenticator.getIBinder();
}
}
and on the manifest we need to add our service with the
<service android:name=".authentication.MyAuthenticatorService">
<intent-filter>
<action android:name="android.accounts.AccountAuthenticator" />
</intent-filter>
<meta-data android:name="android.accounts.AccountAuthenticator"
android:resource="@xml/authenticator" />
</service>
The authenticator.xml that we link as a resource, is
used to define some attributes for our Authenticator.
That’s how it looks:
<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 是唯一的名称用于区分和表明你的账号类型。 任何时候App想要使用我们认证,它需要知道accountType。
其中icon 和 smallIcon是 图标被展示在设备设置页面和账号批准页面
lable 是一个代表你账号的字符串,被列在设备设置页面。