2009-12-27 17 views
9

Estoy intentando usar OAuth en una aplicación de Android. Lo tengo funcionando correctamente, pero a veces me he encontrado con un problema durante la fase de autenticación. En Android, abro el navegador para que el usuario inicie sesión y se autentique. Luego la url de devolución de llamada se redirigirá a mi aplicación.Estado de instancia de OAuth en Android

Aquí está el problema. Mi aplicación tiene un consumidor y proveedor de OAuth como miembros de mi clase principal. Cuando se inicia el navegador para la autenticación, a veces mi actividad principal se descarta para guardar la memoria. Cuando la URL de devolución de llamada relanza mi Actividad principal, el proveedor y el consumidor son instancias nuevas y, por lo tanto, no funcionan cuando intento realizar una solicitud a la API. Si el Activiy principal no se liberó durante la fase de autenticación, entonces todo funciona correctamente porque todavía estoy trabajando con el consumidor y el proveedor originales.

Intenté usar onSaveInstanceState() y onRestoreInstanceState(), pero no se han realizado correctamente. Parece que onRestoreInstanceState() no se llama cuando se maneja mi url de devolución de llamada. Parece ir directamente a onResume().

¿Cuál es el método correcto para persistir el consumidor y el proveedor en este caso?

Respuesta

3

completa Guardar/solución

Aparte de la request_token y token_secret, el estado isOauth10a() es importante para ser restaurada en el proveedor de restauración. Podría haber más información del estado en el futuro. Por lo tanto, me gusta la solución persistir y cargar lo mejor.

Amplié GrkEngineer 's solución para ser más completo. Guarda/restaura tanto el proveedor como el consumidor, maneja todas las excepciones y configura el httpClient al restaurar.

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

He probado este código y funciona.

3

Puedes leer mi old post here. En general, lo que hice fue utilizar referencias estáticas y usar Activity con WebView en lugar de un navegador independiente para mostrar el formulario de autenticación

+0

De hecho, comencé con su publicación anterior. Muy útil. Podría intentar tu solución, pero quería probar algo más para ayudarme a comprender mejor algunas de las cosas de OAuth. Encontré este ejemplo, http://code.google.com/p/jpoco/source/browse/trunk/jpoco-android-app/src/jpoco/android/MainActivity.java?r=346 en el cual él almacenó varios tokens como SharedPreferences para la aplicación. Si quisiera hacer algo similar, ¿qué partes del consumidor y el proveedor necesitaría guardar? También pensé que esto podría ser útil para no tener que volver a autenticarse cada vez que se ejecuta la aplicación. – GrkEngineer

+0

No tiene que volver a autenticarse, simplemente guarde el token y vuelva a utilizarlo. Twitter no caduca. Tiene razón: en mi ejemplo, no estaba intentando comprender cómo se realiza la firma (por ejemplo), sino simplemente cómo usarla. De mi experiencia con DroidIn y de los comentarios que recibo, funciona bastante bien. Como alternativa, veo personas que usan la solución de generación de claves donde el usuario después de la autenticación se presenta con la clave que necesita ingresar en la página de firma de la aplicación, pero eso parece más dolor – Bostone

+0

Sí, su ejemplo fue realmente útil. Supongo que tenía curiosidad por saber si los campos proveedor/consumidor podrían guardarse en el disco usando SharedPreferences. Luego, cuando vuelva la devolución de llamada, puede volver a llenar el proveedor/consumidor con los campos guardados. – GrkEngineer

3

Lo resolví al mantener el objeto proveedor en un archivo. Estoy usando la biblioteca de señalización y tanto el proveedor como el consumidor son serializables.

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

llamo persisten proveedor justo antes de lanzar la vista de la intención del navegador para la autenticación, y el proveedor de restauración en onResume() justo antes de llamar provider.retrieveAccessToken(). Si llama a persistProvider() y loadProvider() en un par de ubicaciones más, también puede hacer que guarde los tokens adecuados después de la autenticación. Esto eliminaría la necesidad de volver a autenticarse (siempre que el token sea válido).

Todavía me gustaría saber qué campos de la clase de proveedor realmente se necesitan para continuar. Podría ser algo lento para serializar todo el objeto.

+0

Mejoré esta solución con éxito. Ver mi respuesta – HRJ

2

Sólo tiene que persistir consumer.getToken() y consumer.getTokenSecret()

Más tarde se puede simplemente volver a crear una nueva consumer(customerKey,customerKeySecret) y consumer.setTokenWithSecret(token, tokenSecret)

Lo que era difícil es averiguar fue la siguiente:

  1. Uso CommonsHttpOAuthConsumer y CommonsHttpOAuthProvider (en android) el DefaultOAuthProvider no funcionará.

  2. Al utilizar HttpURLConnection, no se puede firmar POST peticiones que realizan los parámetros de consulta en la carga útil del mensaje

+0

Sí, eso es información útil sobre CommonsHttpOAuthConsumer vs DefaultOAuthProvider. En cuanto al ahorro de token y secreto del consumidor, esto es todo lo que guardo entre ejecuciones de aplicaciones. La pregunta original trataba más sobre el problema de apagar y reiniciar la aplicación cuando inicias el navegador para iniciar sesión por primera vez. – GrkEngineer

1

que tenía el mismo problema. Todo lo que necesita para persistir es requestToken y tokenSecret que obtiene después de llamar a retrieveRequestToken. En su método onResume(), recree el consumidor y el objeto del proveedor como se describe here. De esta forma no necesita persistir todo el consumidor y los objetos del proveedor y aún podrá recuperar el accessToken.

2

Posiblemente la razón por la que el póster original tuvo problemas para mantener el estado de la instancia es porque el comportamiento predeterminado de Android es iniciar una nueva actividad para cada nuevo intento. Es por eso que GrkEngineer no vio en que se llamara a RestoreInstanceState después de la devolución de llamada web.

El almacenamiento de su token de solicitud como preferencia compartida es una solución para que se pueda acceder desde la nueva actividad que se inicia después de la devolución de llamada web de OAuth.

Intenté originalmente usar las preferencias compartidas y pareció funcionar bien. Sin embargo, no creo que esa sea la mejor solución. Idealmente, desea forzar a Android a devolver la devolución de llamada a su actividad original (explicaré por qué a continuación).

He intentado usar los modos de inicio singleTask y singleInstance para lograr esto con éxito parcial, pero se sentía mal, y los documentos de Android dan a entender que esos modos no se recomiendan para uso general.

Después de analizar detenidamente la documentación y las pruebas, descubrí que usar los siguientes indicadores al crear la intención hace que Android entregue el intento a una instancia existente de la actividad (recreándola si ha sido eliminada).

intent.setFlags (Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP);

La razón por la que necesitaba que la actividad original manejara la devolución de llamada era para poder integrarla con el gestor de cuentas de Android. He utilizado el siguiente ejemplo de ayuda para empezar:

http://developer.android.com/resources/samples/SampleSyncAdapter/index.html

Una de las piezas clave de la integración con el mecanismo AccountManager autenticador es el AccountAuthenticatorResponse que se pasa en su actividad para iniciar el proceso de autenticación.

Encontré que el gran problema con la implementación de esto era mantener una referencia al objeto AccountAuthenticatorResponse. Esto se transfiere a su AuthenticatorActivity y necesita llamar a los métodos una vez que se completa la autenticación para que la UI de las cuentas estándar quede en el estado correcto. Sin embargo, me tocó el mismo problema que GrkEngineer inicialmente golpeó. Cuando intenté reiniciar mi actividad de autenticador de OAuth después de la devolución de llamada de OAuth, siempre obtenía una nueva instancia que había perdido la referencia al objeto AccountAuthenticatorResponse y no veía ninguna forma de persistir en ese objeto.

La clave era usar las banderas de intención que describí anteriormente.

AuthenticatorActivity se inicia utilizando FLAG_ACTIVITY_NEW_TASK por mi AbstractAccountAuthenticator. Recupera el token de solicitud (usando AsyncTask) e inicia el navegador para pedirle al usuario que lo autorice.

OAuthCallbackHandlerActivity está registrada para gestionar mi esquema de devolución de llamada personalizada. Cuando recibe una llamada después de que el usuario concede acceso, realiza una llamada a AuthenticatorActivity utilizando los indicadores Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP intento.

Esto hace que mi AuthenticatorActivity original se reactive. El objeto AccountAuthenticatorResponse todavía está disponible (al igual que el token/secret de la solicitud, que guardé en OnSaveInstanceState). La actividad ahora puede obtener el token de acceso (nuevamente usando AsyncTask) y luego llamar a los métodos de finalización en el objeto AccountAuthenticatorResponse.

La clave para hacer esto fue utilizar los indicadores de intención que mencioné, y también asegurarme de que AuthenticatorActivity se inicia en la tarea de la aplicación en lugar de en la tarea del administrador de la cuenta. FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_SINGLE_TOP solo causará que una instancia existente de una actividad se reutilice si están en la misma tarea. Por lo tanto, si la actividad que desea reanudar se inicia en alguna otra tarea, entonces la instancia original no se volverá a utilizar.

Probé esto en un emulador usando Dev Tools para matar mi AuthenticatorActivity inmediatamente para poder probar el proceso de recreación. Funcionó muy bien utilizando onSaveInstanceState/onRestoreInstanceState para el token/secret de solicitud. Y ni siquiera tuve que preocuparme por restaurar el objeto AccountAuthenticatorResponse. Eso fue restaurado por el propio Android: ¡magia!

Cuestiones relacionadas