Autorizzare l'accesso ai dati utente di Google

L'autenticazione stabilisce l'identità di una persona e viene comunemente definita registrazione o accesso utente. L'autorizzazione è il processo di concessione o rifiuto dell'accesso a dati o risorse. Ad esempio, la tua app richiede il consenso dell'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, devi progettare 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 quando 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 a Drive dell'utente ogni volta che l'utente tocca un pulsante "Salva su Drive".

Separando l'autorizzazione dall'autenticazione, puoi evitare di sopraffare 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 azioni che richiedono l'accesso ai dati utente archiviati da Google, ti consigliamo di utilizzare AuthorizationClient.

Configura il progetto

  1. Apri il progetto in o crea un progetto se non ne hai già uno.
  2. Nella sezione , 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 presentati agli utenti nella schermata del consenso di Accedi con Google durante la 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. In , 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 al .
    2. Fai clic su Crea cliente.
    3. Seleziona il tipo di applicazione Android.
  4. In , 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 server di backend quando comunica con i servizi di autenticazione di Google.
    1. Vai al .
    2. Fai clic su Crea cliente.
    3. Seleziona il tipo Applicazione web.

Dichiarare le dipendenze

Nel file build.gradle del modulo, dichiara le dipendenze utilizzando l'ultima versione della libreria Google Identity Services.

dependencies {
  // ... other dependencies

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

Richiedere le autorizzazioni richieste dalle azioni dell'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.launchIntentSenderRequest.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, in cui 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 (ApiException e) {
                // 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 che invii al backend per lo scambio con un token di accesso e aggiornamento. Per saperne di più, vedi Mantenere l'accesso continuo ai dati dell'utente.

Revocare le autorizzazioni per i dati o le risorse dell'utente

Per revocare l'accesso concesso in precedenza, chiama AuthorizationClient.revokeAccess(). Ad esempio, se l'utente sta rimuovendo il proprio account dalla tua app e alla tua app è stato precedentemente 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));

Svuotare 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 diventare non validi anche per altri motivi. Se ricevi un errore IllegalStateException quando utilizzi un token, svuota la cache locale per assicurarti che la successiva richiesta di autorizzazione 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 utente durante l'autorizzazione

La risposta di autorizzazione non contiene informazioni sull'account utente utilizzato; la risposta contiene 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'email dell'utente, hai le seguenti opzioni:

  • Accedi all'utente con il suo Account Google utilizzando le API Credential Manager prima di chiedere l'autorizzazione. La risposta di autenticazione di Credential Manager include informazioni sull'utente, come l'indirizzo email, e imposta anche l'account predefinito dell'app sull'account selezionato. Se necessario, puoi monitorare questo account nella tua app. Una successiva richiesta di autorizzazione 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 la sezione 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 aver restituito un token di accesso, recupera le informazioni utente effettuando una richiesta HTTP GET all'endpoint userinfo OAuth (https://www.googleapis.com/oauth2/v3/userinfo) utilizzando la tua libreria HTTP preferita e includendo il token di accesso che hai 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 è il UserInfo, limitato agli ambiti richiesti, formattato in JSON.

Autorizzazione da un account non predefinito

Se utilizzi Credential Manager per l'autenticazione ed esegui AuthorizationClient.authorize(), l'account predefinito della tua app è 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 Credential Manager.

Mantenere l'accesso continuo ai dati dell'utente

Se devi accedere ai dati dell'utente dalla tua app, chiama AuthorizationClient.authorize() una sola 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 server di backend, devi richiedere un altro tipo di token chiamato "token di aggiornamento".

I token di accesso sono progettati intenzionalmente per avere una durata breve, ovvero di un'ora. Se un token di accesso viene intercettato o compromesso, il suo periodo di validità limitato riduce al minimo il potenziale uso improprio. Una volta scaduto, 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 durata prolungata che vengono utilizzati da un client per richiedere un token di accesso di breve durata dal server di autorizzazione, quando il vecchio token di accesso è 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 chiedendo "accesso offline", quindi scambiare il codice di autenticazione con un token di aggiornamento sul tuo server. È fondamentale archiviare i token di aggiornamento a lunga durata in modo sicuro sul server perché possono essere utilizzati ripetutamente per ottenere nuovi token di accesso. Pertanto, è fortemente sconsigliato archiviare i token di aggiornamento sul dispositivo per motivi di sicurezza. ma devono essere archiviati nei server di backend dell'app dove avviene lo scambio con un token di accesso.

Dopo l'invio del codice di autorizzazione al server di backend dell'app, puoi scambiarlo con un token di accesso a breve durata sul server e un token di aggiornamento a lunga durata seguendo i passaggi della 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.launchIntentSenderRequest.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"));

Il seguente snippet 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
                }
            });
}