安卓中的OAuth实例状态

9
我正在尝试在Android应用中使用OAuth。我已经成功地完成了它,但有时在认证阶段遇到问题。在Android中,我启动浏览器让用户登录和认证。然后回调URL将重定向回我的应用程序。
这里的问题是:我的应用程序具有OAuth消费者和提供者作为我的主类的成员。当浏览器被启动进行身份验证时,有时会丢弃我的主Activity以节省内存。当回调URL重新启动我的主Activity时,提供者和消费者是新实例,因此当我尝试向API发出请求时无法工作。如果主Activity在认证阶段未被释放,则一切正常,因为我仍在使用原始的消费者和提供者。
我尝试使用onSaveInstanceState()和onRestoreInstanceState(),但没有成功。似乎当处理我的回调URL时,onRestoreInstanceState()不会被调用。似乎直接进入了onResume()。
在这种情况下持久化消费者和提供者的正确方法是什么?
6个回答

3

我通过将提供者对象持久化到文件中来解决了这个问题。我使用的是signpost库,提供者和消费者都是可序列化的。

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();
}

在启动浏览器视图意图进行身份验证之前,我会调用persist provider,并在onResume()中恢复提供程序,然后调用provider.retrieveAccessToken()。如果您在几个位置调用persistProvider()和loadProvider(),还可以在身份验证后保存正确的令牌。这将消除重新认证的需要(只要令牌有效)。

我仍然希望知道提供程序类中实际需要持久化的字段是哪些。序列化整个对象可能会有点慢。


我成功地增强了这个解决方案。请查看我的答案。 - HRJ

3

完整的保存/恢复解决方案

除了request_tokentoken_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();
  }
}

我已经测试了这段代码,它是可行的。


3

您可以在这里阅读我的旧文章。通常情况下,我使用静态引用和使用带有WebView的Activity来显示认证表单,而不是使用独立的浏览器。


我实际上是从您的旧帖子开始的。非常有帮助。我可能会尝试您的解决方案,但我想尝试其他东西来帮助我更好地理解一些OAuth的内容。我发现了这个例子:http://code.google.com/p/jpoco/source/browse/trunk/jpoco-android-app/src/jpoco/android/MainActivity.java?r=346,在其中他将各种令牌存储为应用程序的SharedPreferences。如果我想要做类似的事情,我需要保存消费者和提供者的哪些部分?我还认为这可能有助于不必每次运行应用程序时重新进行身份验证。 - GrkEngineer
你不需要重新进行身份验证,只需保存令牌并重复使用即可。Twitter 不会使其过期。你是对的 - 在我的示例中,我并不是在尝试理解如何进行签名(例如),而只是想知道如何使用它。根据我对 DroidIn 的经验以及收到的反馈 - 它运行得非常好。另外,我看到有人使用密钥生成解决方案,在用户进行身份验证后,会显示一个密钥,然后需要在应用程序签名页面上输入该密钥,但这感觉更加痛苦。 - Bostone
是的,你的例子非常有帮助。我想知道是否可以使用SharedPreferences将提供程序/消费者字段保存到磁盘中。然后当回调返回时,您可以使用保存的字段重新填充提供程序/消费者。 - GrkEngineer
我曾经为此苦苦挣扎,但因为懒惰而发现将它们保存到单例中更容易,但不幸的是,这只在应用程序保持在前台时有效,经常调用浏览器会导致重新启动应用程序线程,然后提供者/消费者就会消失。我发现使用WebView活动比序列化到磁盘更简单。 - Bostone

2
你只需要持久化 consumer.getToken()consumer.getTokenSecret() 之后,你可以简单地重新创建一个新的 consumer(customerKey,customerKeySecret)consumer.setTokenWithSecret(token, tokenSecret) 其中有些棘手的是找到以下内容:
  1. 在 Android 上使用 CommonsHttpOAuthConsumerCommonsHttpOAuthProvider,而不是 DefaultOAuthProvider

  2. 当使用 HttpURLConnection 时,无法对携带查询参数的 POST 请求进行签名。


是的,这是关于CommonsHttpOAuthConsumer与DefaultOAuthProvider的有用信息。至于消费者令牌和密钥的保存,这是我在应用程序运行之间保存的所有内容。最初的问题更多地涉及当您启动浏览器进行首次登录时应用程序关闭和重新启动的问题。 - GrkEngineer

2
可能原帖中存在实例状态问题的原因是,Android 的默认行为是为每个新意图启动一个新活动。这就是为什么在网络回调后 GrkEngineer 没有看到 onRestoreInstanceState 被调用的原因。
将请求令牌存储为共享首选项是一种解决方案,以便可以从 OAuth 网络回调后启动的新活动中访问它。
我最初尝试使用共享首选项,它似乎工作得很好。但是,我认为这不是最佳解决方案。理想情况下,您需要强制 Android 将回调传递给您的原始活动(我将在下面解释原因)。
我尝试使用 singleTask 和 singleInstance 启动模式部分成功地完成了此操作,但感觉不正确,并且 Android 文档暗示这些模式不建议一般使用。
经过大量查找文档和测试,我发现在创建意图时使用以下标志会导致 Android 将意图传递到现有活动的实例中(如果已被销毁,则重新创建)。
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对象的引用,我看不到任何持久化该对象的方法。
关键是使用上面描述的intent标志。
我的AbstractAccountAuthenticator使用FLAG_ACTIVITY_NEW_TASK启动AuthenticatorActivity。它获取请求令牌(使用AsyncTask)并启动浏览器询问用户授权。
OAuthCallbackHandlerActivity注册处理我的自定义回调方案。当用户授权后调用它时,它使用Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP意图调用AuthenticatorActivity。
这将导致我的原始AuthenticatorActivity被重新激活。AccountAuthenticatorResponse对象仍然可用(以及请求令牌/密钥,我在OnSaveInstanceState中保存了它们)。现在,该活动可以获取访问令牌(再次使用AsyncTask),然后调用AccountAuthenticatorResponse对象上的完成方法。
使其工作的关键是使用我提到的意图标志,并确保AuthenticatorActivity在您的应用程序任务中启动,而不是帐户管理器任务中启动。FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_SINGLE_TOP仅会在它们位于同一任务中时才导致现有活动实例被重用。因此,如果要返回的活动在其他任务中启动,则原始实例将无法重用。
我在模拟器上测试了这个过程,使用Dev Tools立即终止我的AuthenticatorActivity,以便测试重建过程。使用onSaveInstanceState/onRestoreInstanceState保存请求令牌/密钥效果很好。我甚至不必担心恢复AccountAuthenticatorResponse对象。Android自己就恢复了它 - 真神奇!

1

我有同样的问题。你只需要持久化调用retrieveRequestToken后获得的requestToken和tokenSecret。 在onResume()方法中,按照这里所述重新创建consumer和provider对象。 这样你就不需要持久化整个consumer和provider对象,仍然能够检索accessToken。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接