android - gae - Autenticación de su cliente a Cloud Endpoints sin el inicio de sesión de una cuenta de Google
crear aplicacion con google app engine (3)
Enfrenté el 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 IOS (Bundle), pero descompilar una aplicación de Android es tan simple ...
La solución que encontré no es perfecta pero hace el trabajo bastante bien:
- En la aplicación de Android, simplemente creo una variable String constante, llamada APIKey, con contenido simple (por ejemplo, "helloworld145698")
- Luego lo encripto con sha1, next md5 y finalmente sha1 (orden y frecuencia de encriptación para usted) y almaceno la variable en SharedPref (para Android) en modo privado (haga esta acción en una clase al azar en su aplicación) Es esto resultado cifrado autorizo en mi back-end!
- En mi backend, simplemente agrego un parámetro (token con nombre 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
}
}
- En Android, ProGuard activo para ofuscar su código . Será realmente ilegible para cualquiera que intente descompilar su aplicación (la ingeniería inversa es realmente dura)
No es la solución segura perfecta, pero funciona, y será realmente realmente difícil encontrar la clave API real para cualquiera que intente leer su código después de la descompilación.
He estado investigando exhaustivamente cómo autenticar a su cliente (Android, iOS, aplicación web) con Cloud Endpoints sin que el usuario tenga que utilizar el inicio de sesión de su cuenta de Google como lo muestra la documentation .
La razón de esto es que quiero asegurar mi API o "bloquearla" solo a mis clientes especificados. A veces tendré una aplicación que no tiene un inicio de sesión de usuario. No me gustaría fastidiar a mi usuario para que inicie sesión solo para que mi API sea segura. O en otras ocasiones, solo quiero administrar mis propios usuarios como si estuvieran en un sitio web y no usar Google+, Facebook o cualquier otra autenticación de inicio de sesión.
Para comenzar, permítanme primero 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 documentation . Después de eso, le mostraré mis hallazgos y un área potencial para una solución con la que necesito ayuda.
(1) Especifique las ID de cliente (ID de cliente) de las aplicaciones autorizadas para realizar solicitudes a su API y (2) agregue un parámetro de Usuario a todos los métodos expuestos para que estén protegidos por 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 Asynctask y asegúrese 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 sucede es que en su aplicación de Android muestra primero el selector de la cuenta de Google, almacenando el correo electrónico de la cuenta de Google en sus preferencias compartidas y luego configurándolo como parte del objeto GoogleAccountCredential
(más información sobre cómo hacerlo here ).
El servidor de Google App Engine recibe su solicitud y la verifica. Si el Cliente Android es uno de los que especificó en la notación @Api
, el servidor @Api
el objeto com.google.api.server.spi.auth.common.User
en su método API. Ahora es su responsabilidad verificar 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 haces esta comprobación, se ejecutará tu método API (un no-no si estás tratando de restringir el acceso a él).
Puede obtener su ANDROID_CLIENT_ID
yendo a Google Developers Console. Allí, proporcionas el nombre del paquete de tu aplicación para Android y el SHA1 que genera para ti un ID de cliente de Android para que uses en tu anotación @Api
(o lo pongas en una clase Constants
como se especifica arriba para uso).
He hecho algunas pruebas exhaustivas con todo lo anterior y esto es lo que encontré:
Si especifica un ID de cliente Android falso o no válido en su anotación @Api
, el objeto User
será null
en su método API. Si está haciendo una comprobación de if (user == null) throw new UnauthorizedException("User is Not Valid");
entonces tu método API no se ejecutará.
Esto es sorprendente porque parece que hay una validación detrás de escena en Cloud Endpoints que verifica si Android Client ID es válido o no. Si no es válido, no devolverá el objeto User
, incluso si el usuario final GoogleAccountCredential
sesión en su cuenta de Google y GoogleAccountCredential
fue válido.
Mi pregunta es, ¿alguien sabe cómo puedo verificar ese tipo de validación de ClientId en mis métodos Cloud Endpoints? ¿Podría esa información pasar en un HttpHeader
por ejemplo?
Otro tipo inyectado en Cloud Endpoints es javax.servlet.http.HttpServletRequest
. Puede obtener la solicitud de esta manera 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 de si la información necesaria está allí o cómo obtenerla.
Ciertamente, en algún lugar debe haber algunos datos que nos @Api
si el Cliente es uno autorizado y especificado en los @Api
clientIds
.
De esta manera, podría bloquear su API a su aplicación de Android (y potencialmente a otros clientes) sin tener que molestar a sus 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, tendrías que pasar null
en el tercer argumento de tu Builder
siguiente manera:
MyApi.Builder builder = new MyApi.Builder (AndroidHttp.newCompatibleTransport (), nuevo AndroidJsonFactory (), nulo)
Luego, en su método API extraiga si la llamada proviene de un cliente autenticado o no, y genere 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 proviene o no de un cliente autenticado y luego, o bien inyecta su objeto User
en el método API o no en función de eso.
¿Podría esa información estar en el encabezado o cuerpo de alguna manera? Si es así, ¿cómo puedo sacarlo para ver si está allí o no en mi método API?
Nota: leo las otras publicaciones sobre este tema. Ofrecen formas de pasar tu propio token de autenticación, lo cual está bien, pero tu .apk aún no será seguro si alguien lo descompila. Creo que si mi hipótesis funciona, podrá bloquear su API de Cloud Endpoints a un cliente sin ningún inicio de sesión.
Autenticación personalizada para Google Cloud Endpoints (en lugar de OAuth2)
Autenticar mi "aplicación" a Google Cloud Endpoints no como un "usuario"
Google Cloud Endpoints sin cuentas de Google
EDITAR: utilizamos Gold Support para Google Cloud Platform y hemos estado hablando de un lado a otro con su equipo de soporte durante semanas. Esta es su respuesta final para nosotros:
"Desafortunadamente, no he tenido suerte con esto. He preguntado por mi equipo y he comprobado toda la documentación. Parece que usar OAuth2 es su única opción. La razón es porque los servidores de punto final manejan la autenticación antes que llega a su aplicación. Esto significa que no podrá desarrollar su propio flujo de autenticación y obtendrá resultados muy similares a los que estaba viendo con los tokens.
Me complacería 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 juntar el resto de la información y enviarla al gerente de producto ".
(cara frowny) - sin embargo, tal vez todavía es posible?
Entonces, no tienes información específica para el usuario, solo quieres asegurarte de que solo tu aplicación pueda comunicarse con tu 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 su aplicación. No puede ser replicado. Si solo permite que sus puntos finales acepten solicitudes de la aplicación de Android, su problema se resolverá.
Dime si esto funciona
Implementé Endpoint Auth usando un encabezado personalizado "Autorización" y funciona muy bien. En mi caso, este token se establece después del inicio de sesión, pero debería funcionar de todos modos con tu aplicación. Verifique sus pruebas porque el valor debería estar allí. La manera de recuperar ese encabezado es de hecho:
String Auth = req.getHeader("Authorization");
Puede dar un paso más y definir sus propias implementaciones de un Authenticator y aplicarlo a sus llamadas API seguras.