23

Ho svolto ricerche approfondite su come autenticare il client (Android, iOS, web-app) con Cloud Endpoint senza che richiede all'utente di utilizzare il proprio account di accesso Google il modo in cui ti mostra lo documentation.Autenticazione del client su Cloud Endpoint senza accesso all'account Google

Il motivo è che voglio proteggere la mia API o "bloccarla" solo ai miei clienti specificati. A volte avrò un'app che non ha un accesso utente. Mi dispiacerebbe infastidire il mio utente per eseguire l'accesso solo in modo che la mia API sia sicura. O altre volte, voglio solo gestire i miei utenti come su un sito Web e non utilizzare Google+, Facebook o qualsiasi altra autenticazione di accesso.

Per iniziare, vorrei prima mostrare il modo in cui è possibile autenticare l'app Android con l'API Cloud Endpoint utilizzando l'account di accesso Google come specificato nello documentation. Dopo ti mostrerò le mie scoperte e una potenziale area per una soluzione con cui ho bisogno di aiuto.

(1) Specificare gli ID client (clientId) delle app autorizzate a effettuare richieste al proprio backend API e (2) aggiungere un parametro Utente a tutti i metodi esposti da proteggere tramite autorizzazione.

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) In Android chiamare il metodo API in un AsyncTask avendo cura di passare nella variabile credential nella 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(); 
     } 
    } 

Quello che sta accadendo è che nella vostra applicazione Android si sta mostrando prima il selettore di account Google, memorizzando l'indirizzo email dell'account Google nelle tue preferenze condivise e successivamente impostandolo come parte dell'oggetto GoogleAccountCredential (ulteriori informazioni su come farlo here).

Il server di Google App Engine riceve la richiesta e la controlla. Se il client Android è uno di quelli specificati nella notazione @Api, il server inietterà l'oggetto com.google.api.server.spi.auth.common.User nel metodo API. È ora tua responsabilità verificare se l'oggetto User è null o meno all'interno del tuo metodo API. Se l'oggetto User è null, è necessario generare un'eccezione nel metodo per impedirne l'esecuzione. Se non lo fai, il tuo metodo API verrà eseguito (un no-no se stai cercando di limitare l'accesso ad esso).

Puoi ottenere il tuo ANDROID_CLIENT_ID andando alla tua Google Developers Console. Lì, fornisci il nome del pacchetto della tua app Android e SHA1 che genera per te un id client Android da utilizzare nell'annotazione @Api (o inseriscilo in una classe Constants come specificato sopra per l'usabilità).

Ho fatto qualche test approfonditi con tutto quanto sopra e qui è quello che ho trovato:

Se si specifica un falso o non valido Android clientId nel vostro @Api annotazione, l'oggetto User sarà null nella vostra Metodo API Se esegui un controllo per if (user == null) throw new UnauthorizedException("User is Not Valid");, il tuo metodo API non verrà eseguito.

Questo è sorprendente perché sembra che ci sia qualche convalida dietro le quinte in corso in Cloud Endpoint che controlla se Android ClientId è valido o meno. Se non è valido, non restituirà l'oggetto User, anche se l'utente finale ha effettuato l'accesso al suo account Google e lo GoogleAccountCredential era valido.

La mia domanda è, qualcuno sa come posso verificare per quel tipo di convalida ClientId da solo nei miei metodi Cloud Endpoints? Ad esempio, queste informazioni potrebbero essere passate in un HttpHeader?

Un altro tipo di input in Cloud Endpoint è lo javax.servlet.http.HttpServletRequest. È possibile ottenere la richiesta di simile nel metodo 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; 
      } 

     } 

ma non sono sicuro se le informazioni necessarie sono lì o come ottenerlo.

Certamente qualche parte ci devono essere alcuni dati che ci dice se il Cliente è un autorizzato e specificato uno in @ApiclientIds.

In questo modo, è possibile bloccare l'API per l'app Android (e potenzialmente altri client) senza dover mai importunare gli utenti finali per accedere (o semplicemente creare il proprio nome utente + password di accesso).

Per tutto questo lavoro però, si dovrà passare null nel terzo argomento della vostra Builder come questo:

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

Quindi nel metodo API estrarre o meno la chiamata da un client autenticato e generare un'eccezione o eseguire il codice desiderato.

So che questo è possibile perché quando si utilizza un GoogleAccountCredential nel Builder, in qualche modo cloud Endpoint sa se la chiamata è venuto da un client autenticato e poi o inietta il suo User oggetto al metodo API o non si basa su questo.

Queste informazioni potrebbero essere nell'intestazione o nel corpo in qualche modo? In tal caso, come posso farlo per verificare in seguito se è presente o meno nel mio metodo API?

Nota: ho letto gli altri post su questo argomento. Offrono modi per passare il proprio token di autenticazione - che va bene - ma il tuo .apk non sarà ancora sicuro se qualcuno lo decompila. Penso che se la mia ipotesi funziona, sarai in grado di bloccare l'API di Cloud Endpoint a un client senza alcun accesso.

Custom Authentication for Google Cloud Endpoints (instead of OAuth2)

Authenticate my "app" to Google Cloud Endpoints not a "user"

Google Cloud Endpoints without Google Accounts

EDIT: Abbiamo usato Gold Support per la piattaforma cloud di Google e hanno parlato avanti e indietro con il loro team di supporto per settimane. Questa è la loro risposta finale per noi:.

"Purtroppo, non ho avuto fortuna su questo ho chiesto in giro la mia squadra e controllato tutta la documentazione Sembra che utilizza OAuth2 è. la tua unica opzione, perché i server endpoint gestiscono l'autenticazione prima che raggiunga la tua app.Ciò significa che non sarebbe in grado di sviluppare il proprio flusso di autenticazione, e otterrebbe risultati molto simile a quello che stavi vedendo con i token.

Sarei felice di inviare una richiesta di funzionalità per voi. Se si potesse fornire qualche informazione in più sul perché il flusso OAuth2 non si lavoro per i vostri clienti, posso mettere il resto delle informazioni insieme e lo sottopone al product manager."

(frowny faccia) - tuttavia, forse è ancora possibile?

risposta

1

Ho implementato l'autenticazione Endpoint utilizzando un'intestazione personalizzata "Autorizzazione" e funziona perfettamente. Nel mio caso questo token è impostato dopo l'accesso ma dovrebbe funzionare lo stesso con il vostro App. Controlla i tuoi test perché il valore dovrebbe essere lì. Il modo per recuperare quell'intestazione è infatti:

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

Si potrebbe fare un ulteriore passo avanti e definire le proprie implementazioni di un Authenticator e applicarlo alle chiamate API sicure.

+0

'req.getHeader (" Autorizzazione ")' è 'null' se non si passa una credenziale di google dal client Android. Ho anche provato a passare la mia implementazione di un 'Authenticator', ma il problema esiste ancora: se qualcuno decompila la tua app, potrebbe spoofare la tua app perché il tuo' Authenticator' è scritto in bianco e nero lì ... Quindi speravo che ci fosse un altro modo ... – Micro

+0

@micror questo non è vero. Ho popolato intestazioni personalizzate come auth con valori definiti dall'utente (ad es. Da un client js angolare) – jirungaray

+0

Quale parte non è vera? Sì, puoi popolare intestazioni personalizzate come l'autenticazione personale, ma il fatto è che la tua app può essere decompilata e falsificata. Per favore, leggi di nuovo la mia domanda - la domanda mostra come ci sia una sorta di autenticazione che Cloud Endpoints sta facendo per verificare il client Android di cui non ci sta dicendo, quindi forse possiamo trovare la risposta. – Micro

0

Quindi non hanno alcuna informazione specifica dell'utente, ma vuole solo garantire che solo la vostra applicazione è in grado di comunicare con il back-end ... Questo è quello che penso,

cambiamento

@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}) 

{ 
... 
} 

l'ID del client viene generato dalla firma della vostra applicazione. Non può essere replicato. Se si consente solo ai propri endpoint di accettare richieste dall'app Android, il problema verrà risolto.

Dimmi se funziona.

+0

Grazie per la risposta, ma questo non funziona. Rimozione di 'Constants.WEB_CLIENT_ID, costanti.IOS_CLIENT_ID, Constants.API_EXPLORER_CLIENT_ID' non fa nulla. – Micro

+0

@MicroR sì, forse è solo per OAuth stesso. Per il tuo problema, avrai bisogno di un'autenticazione personalizzata. Perché non usare qualcosa basato su RSA? –

1

Di fronte allo stesso problema per trovare una soluzione per chiamare la mia API in modo sicuro dai miei endpoint, senza utilizzare l'account Google. Non possiamo decompilare un IOS App (Bundle), ma decompilare un App Android è così semplice ..

La soluzione che ho trovato non è perfetto, ma fare il lavoro abbastanza bene:

  1. su android APP, Ho appena creato una variabile String costante, denominata APIKey, con un semplice contenuto (ad esempio "helloworld145698")
  2. Quindi lo crittografo con sha1, successivo md5 e infine sha1 (ordine e frequenza della crittografia fino a te) e memorizzo il variabile su SharedPref (per Android) in modalità privata (Esegui questa azione su una classe casuale nella tua app) Questo risultato è criptato Autorizzo sul mio Backend!
  3. Sul mio backend, ho solo aggiungere un parametro (dal nome del token per exemple) su ogni richiesta

Esempio:

@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. On android, ProGuard attivo per offuscare il codice. Sarà davvero illeggibile per chiunque tentasse di decompilare la vostra applicazione (reverse engineering è davvero Hardcore)

non è la soluzione sicura perfetto, ma funziona, e sarà davvero (veramente) difficile da trovare la vera chiave API per chiunque tenti di leggere il tuo codice dopo la decompilazione.

+0

Grazie per il suggerimento. – Micro

Problemi correlati