Android 中的 OAuth 实例状态
Posted
技术标签:
【中文标题】Android 中的 OAuth 实例状态【英文标题】:OAuth instance state in Android 【发布时间】:2010-12-30 06:00:05 【问题描述】:我正在尝试在 android 应用中使用 OAuth。我让它正常工作,但有时在身份验证阶段会遇到问题。在 Android 中,我启动浏览器供用户登录和验证。然后回调 url 将重定向回我的应用程序。
这就是问题所在。我的应用程序有一个 OAuth 消费者和提供者作为我的主类的成员。当启动浏览器进行身份验证时,有时我的主 Activity 会被丢弃以节省内存。当回调 url 重新启动我的主 Activity 时,提供者和消费者是新实例,因此当我尝试向 api 发出请求时不起作用。如果在身份验证阶段没有释放主 Activiy,那么一切正常,因为我仍在使用原始消费者和提供者。
我尝试使用 onSaveInstanceState() 和 onRestoreInstanceState(),但没有成功。处理我的回调 url 时似乎没有调用 onRestoreInstanceState() 。似乎直接进入onResume()。
在这种情况下,持久化消费者和提供者的正确方法是什么?
【问题讨论】:
【参考方案1】:完整的保存/恢复解决方案
除了request_token
和token_secret
之外,isOauth10a()
状态对于在提供程序中恢复很重要。未来可能会有更多的状态信息。因此,我最喜欢持久和加载解决方案。
我扩展了 GrkEngineer 的解决方案,使其更加完整。它保存/恢复提供者和消费者,处理所有异常,并在恢复时设置 httpClient。
protected void loadProviderConsumer()
try
FileInputStream fin = this.openFileInput("tmp_provider.dat");
ObjectInputStream ois = new ObjectInputStream(fin);
provider = (CommonsHttpOAuthProvider) ois.readObject();
provider.setHttpClient(httpClient);
ois.close();
fin.close();
fin = this.openFileInput("tmp_consumer.dat");
ois = new ObjectInputStream(fin);
consumer = (CommonsHttpOAuthConsumer) ois.readObject();
ois.close();
fin.close();
Log.d("OAuthTwitter", "Loaded state");
catch (FileNotFoundException e)
e.printStackTrace();
catch (StreamCorruptedException e)
e.printStackTrace();
catch (IOException e)
e.printStackTrace();
catch (ClassNotFoundException e)
e.printStackTrace();
protected void persistProviderConsumer()
try
FileOutputStream fout = this.openFileOutput("tmp_provider.dat", MODE_PRIVATE);
ObjectOutputStream oos = new ObjectOutputStream(fout);
oos.writeObject(provider);
oos.close();
fout.close();
fout = this.openFileOutput("tmp_consumer.dat", MODE_PRIVATE);
oos = new ObjectOutputStream(fout);
oos.writeObject(consumer);
oos.close();
fout.close();
Log.d("OAuthTwitter", "Saved state");
catch (FileNotFoundException e)
e.printStackTrace();
catch (IOException e)
e.printStackTrace();
我已经测试了这段代码,它可以工作。
【讨论】:
【参考方案2】:您可以阅读我的old post here。通常我所做的是使用静态引用并使用带有 WebView 的 Activity 而不是独立浏览器来显示身份验证表单
【讨论】:
我实际上是从你的旧帖子开始的。非常有帮助。我可能会尝试您的解决方案,但我想尝试其他方法来帮助我更好地理解一些 OAuth 内容。我遇到了这个例子,code.google.com/p/jpoco/source/browse/trunk/jpoco-android-app/…,他在其中将各种令牌存储为应用程序的 SharedPreferences。如果我想做类似的事情,我需要保存消费者和提供者的哪些部分?我还认为这可能有助于不必在每次运行应用程序时重新进行身份验证。 您不必重新认证,只需保存令牌并重复使用即可。 Twitter 不会过期。你说得对——在我的例子中,我并不是想了解签名是如何完成的(例如),而只是了解如何使用它。从我使用 DroidIn 的经验和我得到的反馈来看 - 它运行良好。或者,我看到人们使用密钥生成解决方案,在身份验证后向用户提供密钥,他需要在应用程序签名页面上输入该密钥,但感觉更痛苦 是的,你的例子真的很有帮助。我想我很想知道提供者/消费者字段是否可以使用 SharedPreferences 保存到磁盘。然后当回调返回时,您可以使用保存的字段重新填充提供者/消费者。 我为此苦苦挣扎了一段时间,但由于懒惰,发现将这些保存到单例中更容易,不幸的是,这只在您的应用程序处于前台时才有效,调用浏览器通常会导致重新启动应用程序线程,然后提供者/消费者消失了。我发现使用 WebView 活动比序列化到磁盘更简单的解决方案【参考方案3】:我通过将提供程序对象保存到文件来解决这个问题。我正在使用路标库,并且提供者和消费者都是可序列化的。
protected void loadProvider()
FileInputStream fin = this.openFileInput("provider.dat");
ObjectInputStream ois = new ObjectInputStream(fin);
this.provider = (DefaultOAuthProvider) ois.readObject();
ois.close();
consumer = this.provider.getConsumer();
protected void persistProvider()
FileOutputStream fout = this.openFileOutput("provider.dat", MODE_PRIVATE);
ObjectOutputStream oos = new ObjectOutputStream(fout);
oos.writeObject(this.provider);
oos.close();
我在启动浏览器视图意图进行身份验证之前调用持久提供程序,并在调用 provider.retrieveAccessToken() 之前在 onResume() 中恢复提供程序。如果您在多个位置调用 persistProvider() 和 loadProvider(),您还可以让它在身份验证后保存正确的令牌。这将消除重新验证的需要(只要令牌有效)。
我仍然希望知道提供程序类中的哪些字段实际上需要持久化。序列化整个对象可能有点慢。
【讨论】:
我成功地增强了这个解决方案。看我的回答。【参考方案4】:原始发布者在维护实例状态时遇到问题的原因可能是因为 Android 的默认行为是为每个新意图启动一个新活动。这就是为什么 GrkEngineer 在 web 回调之后没有看到 onRestoreInstanceState 被调用的原因。
将您的请求令牌存储为共享首选项是一种解决方案,以便可以从 OAuth Web 回调之后启动的新活动中访问它。
我最初尝试使用共享首选项,它似乎工作正常。但是,我认为这不是最好的解决方案。理想情况下,您希望强制 Android 将回调传递给您的原始活动(我将在下面解释原因)。
我尝试使用 singleTask 和 singleInstance 启动模式来部分成功地完成此操作,但感觉不对,Android 文档暗示不建议将这些模式用于一般用途。
经过大量研究文档和测试后,我发现在创建 Intent 时使用以下标志会导致 Android 将 Intent 传递给 Activity 的现有实例(如果它被杀死则重新创建它)。
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP);
我需要让原始活动处理回调的原因是我可以与 android AccountManager 集成。我使用以下示例开始:
http://developer.android.com/resources/samples/SampleSyncAdapter/index.html
与 AccountManager 身份验证器机制集成的关键部分之一是 AccountAuthenticatorResponse,它被传递到您的活动中以启动身份验证过程。
我发现实现它的一个大问题是保留对 AccountAuthenticatorResponse 对象的引用。这将传递到您的 AuthenticatorActivity 中,一旦身份验证完成,您需要调用它的方法,以便标准帐户 UI 保持正确状态。但是,我遇到了 GrkEngineer 最初遇到的同样问题。当我尝试在 OAuth 回调之后重新启动我的 OAuth 身份验证器活动时,我总是得到一个新实例,该实例丢失了对 AccountAuthenticatorResponse 对象的引用,并且我看不到任何持久化该对象的方法。
关键是使用我上面描述的意图标志。
AuthenticatorActivity 是由我的 AbstractAccountAuthenticator 使用 FLAG_ACTIVITY_NEW_TASK 启动的。它获取请求令牌(使用 AsyncTask)并启动浏览器以请求用户授权。
OAuthCallbackHandlerActivity 已注册以处理我的自定义回调方案。在用户授予访问权限后调用它时,它会使用标志 Intent.FLAG_ACTIVITY_NEW_TASK | 调用 AuthenticatorActivity。 Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP 意图。
这会导致我原来的 AuthenticatorActivity 被重新激活。 AccountAuthenticatorResponse 对象仍然可用(我保存在 OnSaveInstanceState 中的请求令牌/秘密也是如此)。 Activity 现在可以获取访问令牌(再次使用 AsyncTask),然后调用 AccountAuthenticatorResponse 对象的完成方法。
完成这项工作的关键是使用我提到的意图标志,并确保 AuthenticatorActivity 在您的应用程序任务中启动,而不是在帐户管理器任务中启动。 FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_SINGLE_TOP 只会导致活动的现有实例在同一任务中被重用。因此,如果您要返回的活动是在其他任务中启动的,那么原始实例将不会被重用。
我在模拟器上使用开发工具测试了这个,立即终止我的 AuthenticatorActivity,以便我可以测试重新创建过程。将 onSaveInstanceState/onRestoreInstanceState 用于请求令牌/秘密时效果很好。而且我什至不必担心恢复 AccountAuthenticatorResponse 对象。这是由 Android 自己恢复的 - 神奇!
【讨论】:
【参考方案5】:你只需要坚持consumer.getToken()
和consumer.getTokenSecret()
稍后您可以简单地重新创建一个新的consumer(customerKey,customerKeySecret)
和consumer.setTokenWithSecret(token, tokenSecret)
棘手的是要找出以下内容:
使用CommonsHttpOAuthConsumer
和CommonsHttpOAuthProvider
(在安卓上)DefaultOAuthProvider
将不起作用。
使用HttpURLConnection
时,不能对消息载荷中携带查询参数的POST
请求进行签名
【讨论】:
是的,这是关于 CommonsHttpOAuthConsumer 与 DefaultOAuthProvider 的有用信息。至于消费者令牌和秘密的保存,这是我在应用程序运行之间保存的所有内容。最初的问题更多地涉及在您启动浏览器首次登录时应用程序关闭并重新启动的问题。【参考方案6】:我遇到了同样的问题。您只需要保留调用retrieveRequestToken 后得到的requestToken 和tokenSecret。 在您的 onResume() 方法中,按照here 的描述重新创建使用者和提供者对象。 这样你就不需要持久化整个消费者和提供者对象,并且仍然能够检索到 accessToken。
【讨论】:
以上是关于Android 中的 OAuth 实例状态的主要内容,如果未能解决你的问题,请参考以下文章
使用 Google Fit 的 Android 应用程序中的 OAuth 同意屏幕为空白
Android HTTP2 + Oauth2 + Jwt 接口认证实例
在Android应用程序中访问Web中的响应标头和状态代码?