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 @Api
clientIds
.
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?
'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
@micror questo non è vero. Ho popolato intestazioni personalizzate come auth con valori definiti dall'utente (ad es. Da un client js angolare) – jirungaray
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