为了能安全地访问在线服务,必须要验证用户的身份,即需要用户提供证明自己身份的认证信息。 如果一个应用程序要访问第三方服务提供商的数据,那么安全验证问题更加复杂,此时不仅需要验证用户的身份信息,还要验证应用程序的信息,以此来保证应用程序只能访问获得了用户授权的那部分服务和数据。
目前用来解决应用程序和第三方服务提供商授权认证的标准协议是 OAuth2,它本身只提供了一个值,即授权令牌(auth token)。 它代表了用户的授权和应用程序按照用户授权的范围能够进行的数据访问与操作。 本节课展示了怎样通过 OAuth2 协议来认证和访问 Google 的一个服务(服务方必须支持 OAuth2 协议), 虽然本节课的例子只是针对 Google Service,其他的任何第三方服务提供商只要支持 OAuth2 协议, 本文中提到的技术解决方案也同样适用。
使用 OAuth2 的优势:
- 通过获得用户授权的账户来访问在线服务
- 按照用户的意愿进行授权应用程序的访问控制
- 处理授权和认证错误
前期准备
开始进行 OAuth2 认证之前,您首先需要获得服务方提供的 API 信息,具体包括:
- 服务提供方的 API 请求地址
- 访问类型(认证范围-auth scope),一个字符串类型的数据,用来标志应用程序申请的特定类型的认证。例如,auth scope 是 View your tasks,那么就是 read-only(只读)方式访问 Google Tasks 服务, 如果 auth scope 是 Manage Your Tasks,那么就是 read-write(读写)方式访问Google Tasks 服务。
- client id 和 client secret,同样是字符串类型,用来唯一标志访问在线服务的应用程序, 也就是您自己接下来要发开的应用程序,这些信息可以从服务提供商那里直接获得。Google 提供了一个自助服务器来方便用户申请。您可以通过阅读这篇文章 《Getting Started with the Tasks API and OAuth 2.0 on Android》 来了解怎么样申请Google 的 Tasks 服务。
获得访问令牌 (Auth Token)
获得访问令牌的步骤参照下图:
在要获取访问令牌之前,您首先要做的是在 manifest 文件中加入账户管理 ACCOUNT_MANAGER 权限。 如果要利用该令牌来访问在线服务,您还需要网络 INTERNET 权限。
加入的方法参见示例代码:
<manifest ... > <uses-permission android:name="android.permission.ACCOUNT_MANAGER" /> <uses-permission android:name="android.permission.INTERNET" /> ... </manifest>
当权限加入完毕的时候,接下来的工作就是申请令牌了,通过调用方法 AccountManager.getAuthToken() 来获取令牌。
特别注意,因为 AccountManager 中的很多方法都涉及到网络连接,所以大部分方法都是异步调用,那么也就意着,您不能只在一个方法中简单按照 OAuth2 的认证流程来编写程序,而是要拆分成一系列的回调函数(callbacks)来实现。
示例代码:
AccountManager am = AccountManager.get(this); Bundle options = new Bundle(); am.getAuthToken( myAccount_, // Account retrieved using getAccountsByType() "Manage your tasks", // Auth scope options, // Authenticator-specific options this, // Your activity new OnTokenAcquired(), // Callback called when a token is successfully acquired new Handler(new OnError())); // Callback called if an error occurs
在上面的例子中,OnTokenAcquired 类实现了 AccountManagerCallback 接口。 AccountManager 会回调 OnTokenAcquired 类的 run() 方法,并传递一个包含 Bundle 的参数 AccountManagerFuture ,如果成功获取到令牌,那么该令牌信息就会保存在 Bundle 中。
示例代码展示了如何从 Bundle 中取得已申请的令牌:
private class OnTokenAcquired implements AccountManagerCallback<Bundle> { @Override public void run(AccountManagerFuture<Bundle> result) { // Get the result of the operation from the AccountManagerFuture. Bundle bundle = result.getResult(); // The token is a named value in the bundle. The name of the value // is stored in the constant AccountManager.KEY_AUTHTOKEN. token = bundle.getString(AccountManager.KEY_AUTHTOKEN); ... } }
如果一切进展顺利,那么 Bundle 中就包含了合法的令牌值,您可以通过键值 KEY_AUTHTOKEN 直接拿到。不过,有时事情也没有那么顺利,那么需要我们多做如下工作。
重新获得访问令牌 (Auth Token)
第一次申请令牌失败的原因可能有很多,比如:
- 设备本身的问题或者网络的问题导致调用 AccountManager 失败
- 用户不同意授权您的应用程序访问其账户信息
- 设备保存的账户凭据(account credentials)信息不足的问题导致无法访问该账户
- 本地缓存的访问令牌过期了
对于前两条错误,您可以通过简单地显示一条失败信息通知用户来优雅的解决。因为如果是网络掉线或者是用户不同意授权,您的应用程序也对此无能为力,所以也不必过多关注。对于后面的两种情况,处理的时候比较复杂,设计良好的应用程序应当能够自动处理此类错误。
第三种错误是缺少足够的账户凭据信息(insufficient credentials),当这种错误出现的时候,AccountManagerCallback (前面示例代码中实现的 OnTokenAcquired 类) 中的 Bundle 包含了 一个键值为 KEY_INTENT 的 Intent ,您可以通过查询 Bundle 中是有没有包含此键值来判断是否出现了该错误,如果包含了,那么也就意味着 OAuth2 认证过程还需要用户的账户凭据信息(account credentials)才能继续接下来的令牌获取过程。
出现这种情况可能的原因有很多,或许是用户第一次登陆该账户,也可能是用户的账户登录超时了,需要重新登录, 也有可能是用户的账户凭据信息(account credentials)错误,又或者是用户账户启用了两因素认证(two-factor authentication)机制, 也有可能是需要启动照相机来进行视网膜扫描认证。但是不管是哪种具体类型的失败原因,如果您的应用程序需要获得一个合法的访问令牌,那就必须将该 Intent 通知给系统,并且捕获它的返回结果。
示例代码:
private class OnTokenAcquired implements AccountManagerCallback<Bundle> { @Override public void run(AccountManagerFuture<Bundle> result) { ... Intent launch = (Intent) result.get(AccountManager.KEY_INTENT); if (launch != null) { startActivityForResult(launch, 0); return; } } }
请注意在上例中使用的是 startActivityForResult(),那么您就可以在自己的 Activity 中通过实现 onActivityResult() 方法来获得该 Intent 的处理结果。 这一点非常重要,如果您不捕获该 Intent 的处理结果,就不能知道用户是否进行正常认证了。 假如返回的结果为 RESULT_OK, 那么账户凭据(account credentials)信息已经更新,现在有足够的凭据信息来进行接下来的获取访问令牌的过程了,您可以通过再次调用 AccountManager.getAuthToken() 方法来获取新的令牌。
对于最后一种情况令牌的过期,准确来说,这并不算是一种 AccountManager 的错误。 出现此种错误的唯一地方是利用该令牌访问在线服务。 如果要通过利用 AccountManager 来依次访问所有的在线服务,并循环检查所有已存储的令牌是否过期的方法是浪费时间的,代价也比较大。所以,好的解决方法是,当您的应用程序在访问在线服务的时候,捕获这个错误,如果发现令牌过期了,重新申请一个新的即可。 具体的申请流程可以参见上面讲解。
4. 访问在线服务
下面的示例展示了如何访问 google 在线服务。因为 Google 服务的认证是基于标准的 OAuth2 机制,所以这里讨论的技术是广泛适用的。请记住,虽然每个服务提供方可能各不相同,但只要您按照自己的具体情况略作修改即可。
利用 Google APIs 访问 Google 服务,每一次的Http请求,您都需要提供四个值,分别是 API key, client ID, client secret, 和 auth key. 前三个值可以从Google API Console 获得,最后一个值,即访问令牌,通过 AccountManager.getAuthToken() 获得。
You pass these to the Google Server as part of an HTTP request.
示例代码:
URL url = new URL("<a href="https://www.googleapis.com/tasks/v1/users/@me/lists?key">https://www.googleapis.com/tasks/v1/users/@me/lists?key</a>=" + your_api_key); URLConnection conn = (HttpURLConnection) url.openConnection(); conn.addRequestProperty("client_id", your client id); conn.addRequestProperty("client_secret", your client secret); conn.setRequestProperty("Authorization", "OAuth " + token);
如果上面的请求结果返回 401 错误,那么表明您的访问令牌(Token)是无效的。 像上面提到的第四种错误类型,很可能是令牌过期了。解决这个问题非常简单,首先调用AccountManager.invalidateAuthToken() 删除本地缓存的无效令牌,然后调用 AccountManager.getAuthToken() 获取一个新的令牌即可。
因为令牌过期是一个很普遍的情况,解决它也比较容易,很多应用程序只是在每次获取新令牌之前都先将本地令牌设置为过期并清除。对于服务提供商来说,如果申请一个令牌的操作代价比较小,那么您的应用程序可以在第一次调用获取令牌的方法 AccountManager.getAuthToken() 之前,也先调用清除无效令牌的方法 AccountManager.invalidateAuthToken() 。
参考文摘:
http://developer.android.com/training/id-auth/authenticate.html