
为了安全地访问在线服务,用户需要对服务进行身份验证,即,他们需要提供其身份证明。对于访问第三方服务的应用来说,安全问题更为复杂。用户需要通过身份验证才能访问服务,同时,应用需要相应授权才能代表用户执行操作。
OAuth2 协议是处理第三方服务身份验证的业界标准方法。OAuth2 可提供称为身份验证令牌的单个值,它既代表用户的身份,又包含应用代表用户执行操作所需的授权。本课程介绍了如何连接到支持 OAuth2 的 Google 服务器。尽管本课程以 Google 服务为例,但其中介绍的技巧适用于能够正确支持 OAuth2 协议的所有服务。
使用 OAuth2 有下列作用:
- 从用户处获取使用其帐号访问在线服务的许可。
- 代表用户对在线服务进行身份验证。
- 处理身份验证错误。
收集信息
如需开始使用 OAuth2,您需要了解有关待访问 API 的一些信息:
- 待访问服务的网址。
- 身份验证范围,这是一个字符串,定义了应用要求的特定类型的访问权限。例如,对 Google Tasks 的只读权限的身份验证范围为
View your tasks
,而对 Google Tasks 的读写权限的身份验证范围为Manage your tasks
。 - 客户端 ID 和客户端密钥,它们是用于向服务表明应用身份的字符串。您需要直接从服务所有者那里获取这些字符串。Google 提供了一个自助服务系统,用于获取客户端 ID 和客户端密钥。授权和使用 REST API 一文介绍了如何使用此系统来获取这些值,以便与 Google Tasks API 结合使用。
请求互联网权限
对于以 Android 6.0(API 级别 23)及更高版本为目标平台的应用,getAuthToken()
方法本身不需要任何权限。不过,如果要对令牌执行操作,您需要将 INTERNET
权限添加到清单文件中,如以下代码段所示:
<manifest ... > <uses-permission android:name="android.permission.INTERNET" /> ... </manifest>
请求身份验证令牌
如需获取令牌,请调用 AccountManager.getAuthToken()
。
注意:由于某些帐号操作可能涉及网络通信,因此大多数 AccountManager
方法都是异步的。这意味着,您无需在一个函数中完成所有身份验证工作,而是需要将其实现为一系列回调。
以下代码段展示了如何使用一系列回调来获取令牌:
Kotlin
val am: AccountManager = AccountManager.get(this) val options = Bundle() am.getAuthToken( myAccount_, // Account retrieved using getAccountsByType() "Manage your tasks", // Auth scope options, // Authenticator-specific options this, // Your activity OnTokenAcquired(), // Callback called when a token is successfully acquired Handler(OnError()) // Callback called if an error occurs )
Java
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
使用包含 Bundle
的 AccountManagerFuture
对 OnTokenAcquired
调用 run()
。如果调用成功,则令牌位于 Bundle
之内。
以下是从 Bundle
获取令牌的方法:
Kotlin
private class OnTokenAcquired : AccountManagerCallback<Bundle> { override fun run(result: AccountManagerFuture<Bundle>) { // Get the result of the operation from the AccountManagerFuture. val 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. val token: String = bundle.getString(AccountManager.KEY_AUTHTOKEN) } }
Java
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. String token = bundle.getString(AccountManager.KEY_AUTHTOKEN); ... } }
如果一切进展顺利,Bundle
的 KEY_AUTHTOKEN
密钥中会包含一个有效令牌,而您可以继续操作。不过,事情不一定总会那么顺利…
再次请求身份验证令牌
您第一次请求身份验证令牌的操作可能因如下原因而失败:
- 设备或网络出错,导致
AccountManager
出现故障。 - 用户决定不向您的应用授予访问该帐号的权限。
- 存储的帐号凭据不足以获取对该帐号的访问权限。
- 缓存的身份验证令牌已过期。
应用通常只需向用户显示错误消息即可轻松处理前两种情况。如果网络中断或用户决定不授予访问权限,那么应用对此将无能为力。最后两种情况稍微复杂一些,因为运行状况良好的应用理应能自动处理这些故障。
第三种失败情况(凭据不足)通过您在 AccountManagerCallback
(之前一个示例中的 OnTokenAcquired
)中接收的 Bundle
进行传达。如果 Bundle
的 KEY_INTENT
密钥中包含 Intent
,那么身份验证器会告知您它需要直接与用户互动,然后才能为您提供有效的令牌。
身份验证器返回 Intent
的原因或许多种多样。可能是因为这是用户首次登录此帐号。可能用户的帐号已过期,需要重新登录;或者,他们存储的凭据可能不正确。也许该帐号需要进行双重身份验证,或者需要激活相机才能执行 Retina 扫描。原因是哪个其实并不重要。如果您想要获取有效令牌,那么您必须触发 Intent
才能获取该令牌。
Kotlin
private inner class OnTokenAcquired : AccountManagerCallback<Bundle> { override fun run(result: AccountManagerFuture<Bundle>) { val launch: Intent? = result.getResult().get(AccountManager.KEY_INTENT) as? Intent if (launch != null) { startActivityForResult(launch, 0) } } }
Java
private class OnTokenAcquired implements AccountManagerCallback<Bundle> { @Override public void run(AccountManagerFuture<Bundle> result) { ... Intent launch = (Intent) result.getResult().get(AccountManager.KEY_INTENT); if (launch != null) { startActivityForResult(launch, 0); return; } } }
请注意,该示例使用了 startActivityForResult()
,因此您可以通过在自己的 Activity 中实现 onActivityResult()
来获取 Intent
的结果。这一点很重要!如果您没有从身份验证程序的响应 Intent
中获取结果,则无法确定用户是否已成功进行身份验证。如果结果为 RESULT_OK
,则表示身份验证器已更新存储的凭据,因此能够达到您所请求的访问权限级别,而您应该再次调用 AccountManager.getAuthToken()
,以请求新的身份验证令牌。
最后一种情况为令牌已过期,它实际上不属于 AccountManager
出现故障的情况。要了解某个令牌是否已过期,唯一的办法就是与服务器联系;让 AccountManager
不断联网检查所有令牌状态的做法既浪费资源,代价又高昂。因此,只有当应用尝试使用该身份验证令牌访问在线服务时,才会检测到这种失败情况。
连接到在线服务
以下示例展示了如何连接到 Google 服务器。由于 Google 使用业界标准的 OAuth2 协议对请求进行身份验证,因此这里讨论的技巧广泛适用于各种情况。但请注意,服务器各有不同。您可能会发现自己需要根据具体情况对这些说明进行细微的调整。
Google API 要求您为每个请求提供四个值:API 密钥、客户端 ID、客户端密钥和身份验证密钥。前三个来自 Google API 控制台网站。最后一个是通过调用 AccountManager.getAuthToken()
获取的字符串值。您可以在 HTTP 请求中将这些值传递给 Google 服务器。
Kotlin
val url = URL("https://www.googleapis.com/tasks/v1/users/@me/lists?key=$your_api_key") val conn = url.openConnection() as HttpURLConnection conn.apply { addRequestProperty("client_id", your client id) addRequestProperty("client_secret", your client secret) setRequestProperty("Authorization", "OAuth $token") }
Java
URL url = new URL("https://www.googleapis.com/tasks/v1/users/@me/lists?key=" + 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);
如果请求返回 HTTP 错误代码 401,则表示您的令牌已被拒绝。正如上一部分中所述,出现这种情况最常见的原因是令牌已过期。解决方法很简单:调用 AccountManager.invalidateAuthToken()
并再次执行令牌获取流程。
令牌过期的情况很常见,并且修复它们也非常简单,因此,许多应用甚至在请求令牌之前便会假定令牌已过期。如果续订令牌对您的服务器而言耗用资源较少,您不妨在首次调用 AccountManager.getAuthToken()
之前先调用 AccountManager.invalidateAuthToken()
,这样便无需请求两次身份验证令牌。