Autenticación de su cliente en Puntos de extremo de Cloud sin una cuenta de Google

He estado haciendo una extensa investigación sobre cómo autenticar a su cliente (Android, iOS, aplicación web) con Cloud Endpoints sin necesidad de que su usuario utilice su cuenta de Google para acceder a la forma en que la documentación le muestra.

La razón de esto es que quiero proteger mi API o "bloquearla" sólo a mis clientes especificados. A veces tengo una aplicación que no tiene un inicio de sesión de usuario. Odiaría molestar a mi usuario para iniciar sesión ahora solo para que mi API sea segura. O en otras ocasiones, solo quiero administrar mis propios usuarios como en un sitio web y no usar Google+, Facebook o cualquier otra autenticación de inicio de sesión.

Para empezar, permítame mostrar la forma en que puede autenticar su aplicación de Android con su API de Cloud Endpoints utilizando el inicio de sesión de Cuentas de Google como se especifica en la documentación . Después de eso te mostraré mis descubrimientos y un área potencial para una solución que necesito ayuda.

(1) Especifique los ID de cliente (clientIds) de las aplicaciones autorizadas para realizar solicitudes al backend de la API y (2) agregue un parámetro de usuario a todos los métodos expuestos que se van a proteger mediante autorización.

public class Constants { public static final String WEB_CLIENT_ID = "1-web-apps.apps.googleusercontent.com"; public static final String ANDROID_CLIENT_ID = "2-android-apps.googleusercontent.com"; public static final String IOS_CLIENT_ID = "3-ios-apps.googleusercontent.com"; public static final String ANDROID_AUDIENCE = WEB_CLIENT_ID; public static final String EMAIL_SCOPE = "https://www.googleapis.com/auth/userinfo.email"; } import com.google.api.server.spi.auth.common.User; //import for the User object @Api(name = "myApi", version = "v1", namespace = @ApiNamespace(ownerDomain = "${endpointOwnerDomain}", ownerName = "${endpointOwnerDomain}", packagePath="${endpointPackagePath}"), scopes = {Constants.EMAIL_SCOPE}, clientIds = {Constants.WEB_CLIENT_ID, Constants.ANDROID_CLIENT_ID, Constants.IOS_CLIENT_ID, Constants.API_EXPLORER_CLIENT_ID}, audiences = {Constants.ANDROID_AUDIENCE}) public class MyEndpoint { /** A simple endpoint method that takes a name and says Hi back */ @ApiMethod(name = "sayHi") public MyBean sayHi(@Named("name") String name, User user) throws UnauthorizedException { if (user == null) throw new UnauthorizedException("User is Not Valid"); MyBean response = new MyBean(); response.setData("Hi, " + name); return response; } } 

(3) En Android, llame al método API en un Asynctask asegurándose de pasar la variable de credential en el Builder :

 class EndpointsAsyncTask extends AsyncTask<Pair<Context, String>, Void, String> { private static MyApi myApiService = null; private Context context; @Override protected String doInBackground(Pair<Context, String>... params) { credential = GoogleAccountCredential.usingAudience(this, "server:client_id:1-web-app.apps.googleusercontent.com"); credential.setSelectedAccountName(settings.getString(PREF_ACCOUNT_NAME, null)); if(myApiService == null) { // Only do this once MyApi.Builder builder = new MyApi.Builder(AndroidHttp.newCompatibleTransport(), new AndroidJsonFactory(), credential) // options for running against local devappserver // - 10.0.2.2 is localhost's IP address in Android emulator // - turn off compression when running against local devappserver .setRootUrl("http://<your-app-engine-project-id-here>/_ah/api/") .setGoogleClientRequestInitializer(new GoogleClientRequestInitializer() { @Override public void initialize(AbstractGoogleClientRequest<?> abstractGoogleClientRequest) throws IOException { abstractGoogleClientRequest.setDisableGZipContent(true); } }); // end options for devappserver myApiService = builder.build(); } context = params[0].first; String name = params[0].second; try { return myApiService.sayHi(name).execute().getData(); } catch (IOException e) { return e.getMessage(); } } @Override protected void onPostExecute(String result) { Toast.makeText(context, result, Toast.LENGTH_LONG).show(); } } 

Lo que está pasando es que en su aplicación de Android está mostrando primero el selector de cuentas de Google, almacenando el correo electrónico de cuenta de Google en sus preferencias compartidas y luego configurándolo como parte del objeto GoogleAccountCredential (más información sobre cómo hacerlo aquí ).

El servidor de Google App Engine recibe su solicitud y la comprueba. Si el cliente Android es uno de los que especificó en la notación @Api , el servidor inyectará el objeto com.google.api.server.spi.auth.common.User en el método API. Ahora es su responsabilidad comprobar si ese objeto de User es null o no dentro de su método API. Si el objeto User es null , debe lanzar una excepción en su método para evitar que se ejecute. Si no realiza esta comprobación, su método API se ejecutará (un no-no si está intentando restringir el acceso a él).

Puedes obtener tu ANDROID_CLIENT_ID a tu consola de desarrolladores de Google. Allí, usted proporciona el nombre del paquete de su aplicación Android y el SHA1 que genera para usted un ID de cliente android para que lo use en su anotación @Api (o póngalo en una clase Constants como se especificó anteriormente para usabilidad).

He hecho algunas pruebas extensas con todo lo anterior y aquí es lo que encontré:

Si especifica una clientId de Android Android falsa o no válida en su anotación @Api , el objeto User será null en su método API. Si está realizando una comprobación para if (user == null) throw new UnauthorizedException("User is Not Valid"); Entonces su método API no se ejecutará.

Esto es sorprendente, ya que parece que hay algunos tras bambalinas de validación en curso en Cloud Endpoints que comprueban si el Android ClientId es válido o no. Si no es válido, no devolverá el objeto User , aunque el usuario final haya iniciado sesión en su cuenta de Google y el valor de GoogleAccountCredential sido válido.

Mi pregunta es, ¿alguien sabe cómo puedo comprobar ese tipo de validación de ClientId por mi cuenta en mis métodos de Cloud Endpoints? ¿Podría esa información ser pasado alrededor en un HttpHeader por ejemplo?

Otro tipo inyectado en Cloud Endpoints es javax.servlet.http.HttpServletRequest . Puede obtener la solicitud como esta en su método API:

 @ApiMethod(name = "sayHi") public MyBean sayHi(@Named("name") String name, HttpServletRequest req) throws UnauthorizedException { String Auth = req.getHeader("Authorization");//always null based on my tests MyBean response = new MyBean(); response.setData("Hi, " + name); return response; } } 

Pero no estoy seguro si la información necesaria está allí o cómo conseguirla.

Ciertamente en algún lugar debe haber algunos datos que nos dice si el cliente es un autorizado y especificado en el @Api clientIds .

De esta forma, puede bloquear su API a su aplicación de Android (y potencialmente a otros clientes) sin tener que molestar a los usuarios finales para iniciar sesión (o simplemente crear su propio nombre de usuario simple + contraseña de inicio de sesión).

Para que todo esto funcione sin embargo, tendrías que pasar en null en el tercer argumento de tu Builder así:

MyApi.Builder builder = new MyApi.Builder (AndroidHttp.newCompatibleTransport (), nuevo AndroidJsonFactory (), null)

A continuación, en el método API, extraiga si la llamada procede de un cliente autenticado y lance una excepción o ejecute el código que desee.

Sé que esto es posible porque al usar un GoogleAccountCredential en el Builder , de alguna manera Cloud Endpoints sabe si la llamada provenía de un cliente autenticado y, a continuación, inyecta su objeto User en el método API o no basado en eso.

¿Podría esa información estar en la cabecera o el cuerpo de alguna manera? Si es así, ¿cómo puedo obtenerlo para comprobar más tarde si está allí o no en mi método API?

Nota: he leído las otras publicaciones sobre este tema. Ofrecen formas de pasar su propio token de autenticación, lo cual está bien, pero su .apk todavía no estará seguro si alguien lo descompila. Creo que si mi hipótesis funciona, usted será capaz de bloquear su Cloud Endpoints API a un cliente sin ningún inicio de sesión.

Autenticación personalizada para puntos finales de Google Cloud (en lugar de OAuth2)

Autenticar mi "aplicación" en Google Cloud Endpoints no es un "usuario"

Puntos finales de Google Cloud sin cuentas de Google

EDIT: Utilizamos Soporte de Oro para Google Cloud Platform y hemos estado hablando con su equipo de soporte durante semanas. Esta es su respuesta final para nosotros:

"Por desgracia, no he tenido suerte en esto.He preguntado por mi equipo, y comprobado toda la documentación.Parece que el uso de OAuth2 es su única opción.La razón es porque los servidores de punto final de manejar la autenticación antes de que Llega a su aplicación.Esto significa que no sería capaz de desarrollar su propio flujo de autenticación, y obtendría resultados muy parecidos a lo que estaba viendo con las fichas.

Estaré encantado de enviar una solicitud de función para usted. Si pudiera proporcionar un poco más de información sobre por qué el flujo de OAuth2 no funciona para sus clientes, puedo poner el resto de la información en conjunto y enviarla al gerente de producto.

(Frowny cara) – sin embargo, tal vez todavía es posible?

He implementado Endpoint Auth utilizando una cabecera personalizada "Autorización" y funciona muy bien. En mi caso, este token se establece después de iniciar sesión, pero debería funcionar igual con tu aplicación. Compruebe sus pruebas porque el valor debe estar allí. La manera de recuperar ese encabezado es de hecho:

String Auth = req.getHeader("Authorization");

Podría dar un paso más allá y definir sus propias implementaciones de un autenticador y aplicarlo a sus llamadas de API seguras.

Se enfrentó al mismo problema para encontrar una solución para llamar a mi API de forma segura desde mis puntos finales, sin usar la cuenta de Google . No podemos descompilar una aplicación de IOS (Bundle), pero descompilar una aplicación de Android es tan simple.

La solución que encontré no es perfecta, pero hacer el trabajo bastante bueno:

  1. En la aplicación android, sólo creo una variable String constante, denominada APIKey, con simplemente contenido (Por ejemplo "helloworld145698")
  2. Entonces lo cifro con sha1, md5 siguiente, y finalmente sha1 (orden y frecuencia del cifrado hasta usted) y almacena la variable en SharedPref (para el androide) en modo privado (hace esta acción en una clase al azar en su app) Resultado cifrado Autorizo en mi Backend!
  3. En mi backend, me acaba de añadir un parámetro (llamado token por ejemplo) en cada solicitud

Ejemplo:

  @ApiMethod(name = "sayHi") public void sayHi(@Named("name") String name, @Named("Token") String token) { if (token == tokenStoreOnAPIServer) { //Allow it } else { //Refuse it and print error } } 
  1. En android, ProGuard activo para ofuscar su código . Será realmente ilegible para cualquier persona que intentó descompilar su aplicación (la ingeniería inversa es realmente hardcore)

No es la solución segura perfecta, pero funciona, y será realmente muy ( muy ) difícil encontrar la clave real de la API para cualquiera que intente leer su código después de la descompilación.

Así que usted no tiene ninguna información específica del usuario, pero sólo quiere asegurarse de que sólo su aplicación es capaz de comunicarse con su backend … Esto es lo que creo,

cambio

 @Api(name = "myApi", version = "v1", namespace = @ApiNamespace(ownerDomain = "${endpointOwnerDomain}", ownerName = "${endpointOwnerDomain}", packagePath="${endpointPackagePath}"), scopes = {Constants.EMAIL_SCOPE}, clientIds = {Constants.WEB_CLIENT_ID, Constants.ANDROID_CLIENT_ID, Constants.IOS_CLIENT_ID, Constants.API_EXPLORER_CLIENT_ID}, audiences = {Constants.ANDROID_AUDIENCE}) { ... } 

a

 @Api(name = "myApi", version = "v1", namespace = @ApiNamespace(ownerDomain = "${endpointOwnerDomain}", ownerName = "${endpointOwnerDomain}", packagePath="${endpointPackagePath}"), scopes = {Constants.EMAIL_SCOPE}, clientIds = {Constants.ANDROID_CLIENT_ID}, audiences = {Constants.ANDROID_AUDIENCE}) { ... } 

El ID de cliente se genera a partir de la firma de la aplicación. No se puede replicar. Si solo permite que sus puntos finales acepten solicitudes de la aplicación de Android, su problema se solucionaría.

Dime si esto funciona.

  • ¿Cómo puede detectar Google una solicitud desde un WebView?
  • GoogleAuthException al obtener un token de acceso con ClientID
  • Acceso a la API de Gmail mediante Android
  • GoogleAuthException: Desconocido (android)
  • Obtener nombre de usuario, avatar de google account
  • Consumir sitio WebAPI2 desde un cliente Android con autenticación de Google
  • ¿Cómo puedo evitar el Administrador de Cuentas de Chrome de Android para OAuth?
  • Google Plus SignIn / oAuth2 - lanzamiento del lado del servidor TokenResponseException: 401 no autorizado
  • ¿Cómo puedo verificar los símbolos generados por Android en mi servidor mediante PHP?
  • Permisos de concesión de API de GMail antes de la excepción UserRecoverableAuthUIE
  • Identidad de sitios cruzados de Google y de Google
  • FlipAndroid es un fan de Google para Android, Todo sobre Android Phones, Android Wear, Android Dev y Aplicaciones para Android Aplicaciones.