Autorizzare l'accesso ai dati utente di Google

L'autenticazione stabilisce l'identità di una persona ed è comunemente definita registrazione o accesso dell'utente. L'autorizzazione è il processo di concessione o rifiuto dell'accesso a dati o risorse. Ad esempio, la tua app richiede il consenso di un utente per accedere al suo Google Drive.

Le chiamate di autenticazione e autorizzazione devono essere due flussi separati e distinti in base alle esigenze dell'app.

Se la tua app ha funzionalità che possono utilizzare i dati delle API di Google, ma non sono richieste come parte delle funzionalità principali dell'app, progetta l'app in modo che possa gestire correttamente i casi in cui i dati delle API non sono accessibili. Ad esempio, potresti nascondere un elenco di file salvati di recente se l'utente non ha concesso l'accesso a Drive.

Devi richiedere l'accesso agli ambiti necessari per accedere alle API di Google solo quando l'utente esegue un'azione che richiede l'accesso a una determinata API. Ad esempio, devi richiedere l'autorizzazione per accedere al Drive dell'utente ogni volta che l'utente tocca un pulsante Salva su Drive.

Separando l'autorizzazione dall'autenticazione, puoi evitare di sovraccaricare i nuovi utenti o di confonderli sul motivo per cui vengono richieste determinate autorizzazioni.

Per l'autenticazione, ti consigliamo di utilizzare l'API Credential Manager. Per autorizzare le azioni che richiedono l'accesso ai dati utente archiviati da Google, ti consigliamo di utilizzare AuthorizationClient.

Configura il progetto nella console Google Cloud

  1. Apri il progetto nella console Cloud o creane uno se non l'hai già fatto.
  2. Nella pagina Branding, assicurati che tutte le informazioni siano complete e accurate.
    1. Assicurati che alla tua app siano assegnati un nome, un logo e una home page corretti. Questi valori verranno mostrati agli utenti nella schermata per il consenso di Accedi con Google al momento della registrazione e nella schermata App e servizi di terze parti.
    2. Assicurati di aver specificato gli URL delle norme sulla privacy e dei Termini di servizio della tua app.
  3. Nella pagina Client, crea un ID client Android per la tua app, se non ne hai già uno. Dovrai specificare il nome del pacchetto e la firma SHA-1 della tua app.
    1. Vai alla pagina Clienti.
    2. Fai clic su Crea client.
    3. Seleziona il tipo di applicazione Android.
    4. Inserisci un nome per il client OAuth. Questo nome viene visualizzato nella pagina Clienti del progetto per identificare il client.
    5. Inserisci il nome del pacchetto della tua app per Android. Questo valore è definito nell'attributo package dell'elemento <manifest> nel file AndroidManifest.xml.
    6. Inserisci la fingerprint del certificato di firma SHA-1 di App Distribution.
    7. Se la tua app utilizza la firma dell'app di Google Play, copia la fingerprint SHA-1 dalla pagina di firma dell'app di Play Console.
    8. Se gestisci il tuo archivio chiavi e le tue chiavi di firma, utilizza l'utilità keytool inclusa in Java per stampare le informazioni del certificato in un formato leggibile. Copia il valore SHA-1 nella sezione Certificate fingerprints dell'output di keytool. Per saperne di più, consulta Autenticazione del client nella documentazione delle API di Google per Android.
    9. (Facoltativo) Verifica la proprietà della tua app per Android.
  4. Nella pagina Clienti, crea un nuovo ID client "Applicazione web", se non l'hai ancora fatto. Per il momento, puoi ignorare i campi "Origini JavaScript autorizzate" e "URI di reindirizzamento autorizzati". Questo ID client verrà utilizzato per identificare il tuo server di backend quando comunica con i servizi di autenticazione di Google.
    1. Vai alla pagina Clienti.
    2. Fai clic su Crea client.
    3. Seleziona il tipo Applicazione web.

Verifica la proprietà dell'app

Puoi verificare la proprietà della tua applicazione per ridurre il rischio di furto d'identità dell'app.

Per completare la procedura di verifica, puoi utilizzare il tuo account sviluppatore Google Play, se ne hai uno, e la tua app è registrata su Google Play Console. Per superare la verifica, devono essere soddisfatti i seguenti requisiti:

  • Devi avere un'applicazione registrata in Google Play Console con lo stesso nome di pacchetto e lo stesso fingerprint del certificato di firma SHA-1 del client OAuth Android per cui stai completando la verifica.
  • Devi disporre dell'autorizzazione Amministratore per l'app in Google Play Console. Scopri di più sulla gestione degli accessi in Google Play Console.

Nella sezione Verifica la proprietà dell'app del client Android, fai clic sul pulsante Verifica la proprietà per completare la procedura di verifica.

Se la verifica va a buon fine, viene visualizzata una notifica per confermare l'esito positivo della procedura di verifica. In caso contrario, verrà visualizzato un messaggio di errore.

Per risolvere un problema di verifica, prova i seguenti passaggi:

  • Assicurati che l'app che stai verificando sia un'app registrata in Google Play Console.
  • Assicurati di disporre dell'autorizzazione Amministratore per l'app in Google Play Console.

Dichiarazione di dipendenze

Nel file build.gradle del modulo, dichiara le dipendenze utilizzando l'ultima versione della libreria di Servizi di identità Google.

dependencies {
  // ... other dependencies

  implementation "com.google.android.gms:play-services-auth:22.0.0"
}

Richiedi le autorizzazioni necessarie per le azioni utente

Ogni volta che un utente esegue un'azione che richiede un ambito aggiuntivo, chiama AuthorizationClient.authorize(). Ad esempio, se un utente esegue un'azione che richiede l'accesso allo spazio di archiviazione dell'app Drive, procedi nel seguente modo:

Kotlin

val requestedScopes: List<Scope> = listOf(DriveScopes.DRIVE_FILE)
val authorizationRequest = AuthorizationRequest.builder()
    .setRequestedScopes(requestedScopes)
    .build()

Identity.getAuthorizationClient(activity)
    .authorize(authorizationRequestBuilder.build())
    .addOnSuccessListener { authorizationResult ->
        if (authorizationResult.hasResolution()) {
            val pendingIntent = authorizationResult.pendingIntent
            // Access needs to be granted by the user
            startAuthorizationIntent.launch(IntentSenderRequest.Builder(pendingIntent!!.intentSender).build())
        } else {
            // Access was previously granted, continue with user action
            saveToDriveAppFolder(authorizationResult);
        }
    }
    .addOnFailureListener { e -> Log.e(TAG, "Failed to authorize", e) }

Java

List<Scopes> requestedScopes = Arrays.asList(DriveScopes.DRIVE_FILE);
AuthorizationRequest authorizationRequest = AuthorizationRequest.builder()
    .setRequestedScopes(requestedScopes)
    .build();

Identity.getAuthorizationClient(activity)
    .authorize(authorizationRequest)
    .addOnSuccessListener(authorizationResult -> {
        if (authorizationResult.hasResolution()) {
            // Access needs to be granted by the user
            startAuthorizationIntent.launch(
                new IntentSenderRequest.Builder(
                    authorizationResult.getPendingIntent().getIntentSender()
                ).build()
            );
        } else {
            // Access was previously granted, continue with user action
            saveToDriveAppFolder(authorizationResult);
        }
    })
    .addOnFailureListener(e -> Log.e(TAG, "Failed to authorize", e));

Quando definisci ActivityResultLauncher, gestisci la risposta come mostrato nello snippet seguente, dove supponiamo che venga eseguita in un frammento. Il codice verifica che le autorizzazioni richieste siano state concesse correttamente, quindi esegue l'azione dell'utente.

Kotlin

private lateinit var startAuthorizationIntent: ActivityResultLauncher<IntentSenderRequest>

override fun onCreateView(
    inflater: LayoutInflater,
    container: ViewGroup?,
    savedInstanceState: Bundle?,
): View? {
    // ...
    startAuthorizationIntent =
        registerForActivityResult(ActivityResultContracts.StartIntentSenderForResult()) { activityResult ->
            try {
                // extract the result
                val authorizationResult = Identity.getAuthorizationClient(requireContext())
                    .getAuthorizationResultFromIntent(activityResult.data)
                // continue with user action
                saveToDriveAppFolder(authorizationResult);
            } catch (e: ApiException) {
                // log exception
            }
        }
}

Java

private ActivityResultLauncher<IntentSenderRequest> startAuthorizationIntent;

@Override
public View onCreateView(
    @NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
// ...
startAuthorizationIntent =
    registerForActivityResult(
        new ActivityResultContracts.StartIntentSenderForResult(),
        activityResult -> {
            try {
            // extract the result
            AuthorizationResult authorizationResult =
                Identity.getAuthorizationClient(requireActivity())
                    .getAuthorizationResultFromIntent(activityResult.getData());
            // continue with user action
            saveToDriveAppFolder(authorizationResult);
            } catch (ApiException e) {
            // log exception
            }
        });
}

Se accedi alle API di Google lato server, chiama il metodo getServerAuthCode() da AuthorizationResult per ottenere un codice di autorizzazione da inviare al backend per scambiarlo con un token di accesso e un token di aggiornamento. Per scoprire di più, consulta Mantieni l'accesso continuo ai dati dell'utente.

Revocare le autorizzazioni per i dati o le risorse degli utenti

Per revocare l'accesso concesso in precedenza, chiama AuthorizationClient.revokeAccess(). Ad esempio, se l'utente rimuove il proprio account dalla tua app e in precedenza alla tua app era stato concesso l'accesso a DriveScopes.DRIVE_FILE, utilizza il seguente codice per revocare l'accesso:

Kotlin

val requestedScopes: MutableList<Scope> = mutableListOf(DriveScopes.DRIVE_FILE)
RevokeAccessRequest revokeAccessRequest = RevokeAccessRequest.builder()
    .setAccount(account)
    .setScopes(requestedScopes)
    .build()

Identity.getAuthorizationClient(activity)
    .revokeAccess(revokeAccessRequest)
    .addOnSuccessListener { Log.i(TAG, "Successfully revoked access") }
    .addOnFailureListener { e -> Log.e(TAG, "Failed to revoke access", e) }

Java

List<Scopes> requestedScopes = Arrays.asList(DriveScopes.DRIVE_FILE);
RevokeAccessRequest revokeAccessRequest = RevokeAccessRequest.builder()
    .setAccount(account)
    .setScopes(requestedScopes)
    .build();

Identity.getAuthorizationClient(activity)
    .revokeAccess(revokeAccessRequest)
    .addOnSuccessListener(unused -> Log.i(TAG, "Successfully revoked access"))
    .addOnFailureListener(e -> Log.e(TAG, "Failed to revoke access", e));

Svuota la cache dei token

I token di accesso OAuth vengono memorizzati nella cache locale al momento della ricezione dal server, velocizzando l'accesso e riducendo le chiamate di rete. Questi token vengono eliminati automaticamente dalla cache alla scadenza, ma possono anche diventare non validi per altri motivi. Se ricevi un errore IllegalStateException quando utilizzi un token, svuota la cache locale per assicurarti che la richiesta di autorizzazione successiva per un token di accesso venga inviata al server OAuth. Il seguente snippet rimuove invalidAccessToken dalla cache locale:

Kotlin

Identity.getAuthorizationClient(activity)
    .clearToken(ClearTokenRequest.builder().setToken(invalidAccessToken).build())
    .addOnSuccessListener { Log.i(TAG, "Successfully removed the token from the cache") }
    .addOnFailureListener{ e -> Log.e(TAG, "Failed to clear token", e) }

Java

Identity.getAuthorizationClient(activity)
    .clearToken(ClearTokenRequest.builder().setToken(invalidAccessToken).build())
    .addOnSuccessListener(unused -> Log.i(TAG, "Successfully removed the token from the cache"))
    .addOnFailureListener(e -> Log.e(TAG, "Failed to clear the token cache", e));

Recuperare le informazioni dell'utente durante l'autorizzazione

La risposta di autorizzazione non contiene informazioni sull'account utente utilizzato, ma solo un token per gli ambiti richiesti. Ad esempio, la risposta per ottenere un token di accesso per accedere a Google Drive di un utente non rivela l'identità dell'account selezionato dall'utente, anche se può essere utilizzata per accedere ai file sul Drive dell'utente. Per ottenere informazioni come il nome o l'indirizzo email dell'utente, hai le seguenti opzioni:

  • Fai accedere l'utente con il suo Account Google utilizzando le API Credential Manager prima di chiedere l'autorizzazione. La risposta di autenticazione di Gestore delle credenziali include i dati dell'utente, come l'indirizzo email, e imposta l'account predefinito dell'app sull'account selezionato. Se necessario, puoi monitorare questo account nella tua app. Una richiesta di autorizzazione successiva utilizza l'account come predefinito e salta il passaggio di selezione dell'account nel flusso di autorizzazione. Per utilizzare un account diverso per l'autorizzazione, consulta Autorizzazione da un account non predefinito.

  • Nella richiesta di autorizzazione, oltre agli ambiti che ti interessano (ad esempio Drive scope), richiedi gli ambiti userinfo, profile e openid. Dopo che è stato restituito un token di accesso, ottieni le informazioni utente effettuando una richiesta HTTP GET all'endpoint userinfo di OAuth (https://www.googleapis.com/oauth2/v3/userinfo) utilizzando la tua libreria HTTP preferita e includendo il token di accesso che avevi ricevuto nell'intestazione, equivalente al seguente comando curl:

    curl -X GET \ "https://www.googleapis.com/oauth2/v1/userinfo?alt=json" \ -H "Authorization: Bearer $TOKEN"
    

    La risposta è l'UserInfo, limitato agli ambiti richiesti, formattato in JSON.

Autorizzazione da un account non predefinito

Se utilizzi Gestore delle credenziali per l'autenticazione ed esegui AuthorizationClient.authorize(), l'account predefinito della tua app viene impostato su quello selezionato dall'utente. Ciò significa che tutte le chiamate successive per l'autorizzazione utilizzano questo account predefinito. Per forzare la visualizzazione del selettore di account, disconnetti l'utente dall'app utilizzando l'API clearCredentialState() di Gestore delle credenziali.

Mantenere l'accesso continuo ai dati dell'utente

Se devi accedere ai dati dell'utente dalla tua app, chiama AuthorizationClient.authorize() una volta. Nelle sessioni successive, e finché le autorizzazioni concesse non vengono rimosse dall'utente, chiama lo stesso metodo per ottenere un token di accesso per raggiungere i tuoi obiettivi, senza alcuna interazione dell'utente. Se invece devi accedere ai dati dell'utente in modalità offline dal tuo server di backend, devi richiedere un tipo di token diverso, chiamato "token di aggiornamento".

I token di accesso sono progettati intenzionalmente per essere di breve durata e hanno una durata di un'ora. Se un token di accesso viene intercettato o compromesso, la sua finestra di validità limitata riduce al minimo il potenziale uso improprio. Dopo la scadenza, il token diventa non valido e qualsiasi tentativo di utilizzarlo verrà rifiutato dal server delle risorse. Poiché i token di accesso hanno una durata breve, i server utilizzano i token di aggiornamento per mantenere l'accesso continuo ai dati di un utente. I token di aggiornamento sono token con una lunga durata che vengono utilizzati da un client per richiedere un token di accesso di breve durata al server di autorizzazione, quando il token di accesso precedente è scaduto, senza alcuna interazione dell'utente.

Per ottenere un token di aggiornamento, devi prima ottenere un codice di autenticazione (o codice di autorizzazione) durante il passaggio di autorizzazione nella tua app richiedendo l'"accesso offline", quindi scambiare il codice di autenticazione con un token di aggiornamento sul tuo server. È fondamentale archiviare in modo sicuro i token di aggiornamento di lunga durata sul tuo server, perché possono essere utilizzati ripetutamente per ottenere nuovi token di accesso. Pertanto, per motivi di sicurezza, è sconsigliato archiviare i token di aggiornamento sul dispositivo. Devono invece essere archiviati nei server di backend dell'app, dove avviene lo scambio con un token di accesso.

Dopo che il codice di autenticazione è stato inviato al server di backend della tua app, puoi scambiarlo con un token di accesso di breve durata sul server e un token di aggiornamento di lunga durata seguendo i passaggi descritti nella guida all'autorizzazione dell'account. Questo scambio deve avvenire solo nel backend della tua app.

Kotlin

// Ask for offline access during the first authorization request
val authorizationRequest = AuthorizationRequest.builder()
    .setRequestedScopes(requestedScopes)
    .requestOfflineAccess(serverClientId)
    .build()

Identity.getAuthorizationClient(activity)
    .authorize(authorizationRequest)
    .addOnSuccessListener { authorizationResult ->
        startAuthorizationIntent.launch(IntentSenderRequest.Builder(
            pendingIntent!!.intentSender
        ).build())
    }
    .addOnFailureListener { e -> Log.e(TAG, "Failed to authorize", e) }

Java

// Ask for offline access during the first authorization request
AuthorizationRequest authorizationRequest = AuthorizationRequest.builder()
    .setRequestedScopes(requestedScopes)
    .requestOfflineAccess(serverClientId)
    .build();

Identity.getAuthorizationClient(getContext())
    .authorize(authorizationRequest)
    .addOnSuccessListener(authorizationResult -> {
        startAuthorizationIntent.launch(
            new IntentSenderRequest.Builder(
                authorizationResult.getPendingIntent().getIntentSender()
            ).build()
        );
    })
    .addOnFailureListener(e -> Log.e(TAG, "Failed to authorize"));

Lo snippet seguente presuppone che l'autorizzazione venga avviata da un frammento.

Kotlin

private lateinit var startAuthorizationIntent: ActivityResultLauncher<IntentSenderRequest>

override fun onCreateView(
    inflater: LayoutInflater,
    container: ViewGroup?,
    savedInstanceState: Bundle?,
): View? {
    // ...
    startAuthorizationIntent =
        registerForActivityResult(ActivityResultContracts.StartIntentSenderForResult()) { activityResult ->
            try {
                val authorizationResult = Identity.getAuthorizationClient(requireContext())
                    .getAuthorizationResultFromIntent(activityResult.data)
                // short-lived access token
                accessToken = authorizationResult.accessToken
                // store the authorization code used for getting a refresh token safely to your app's backend server
                val authCode: String = authorizationResult.serverAuthCode
                storeAuthCodeSafely(authCode)
            } catch (e: ApiException) {
                // log exception
            }
        }
}

Java

private ActivityResultLauncher<IntentSenderRequest> startAuthorizationIntent;

@Override
public View onCreateView(
    @NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
    // ...
    startAuthorizationIntent =
        registerForActivityResult(
            new ActivityResultContracts.StartIntentSenderForResult(),
            activityResult -> {
                try {
                    AuthorizationResult authorizationResult =
                        Identity.getAuthorizationClient(requireActivity())
                            .getAuthorizationResultFromIntent(activityResult.getData());
                    // short-lived access token
                    accessToken = authorizationResult.getAccessToken();
                    // store the authorization code used for getting a refresh token safely to your app's backend server
                    String authCode = authorizationResult.getServerAuthCode()
                    storeAuthCodeSafely(authCode);
                } catch (ApiException e) {
                    // log exception
                }
            });
}