Utilizzo di OAuth 2.0 con la libreria client delle API di Google per Java

Panoramica

Finalità: questo documento spiega come utilizzare la classe di utilità GoogleCredential per eseguire l'autorizzazione OAuth 2.0 con i servizi Google. Per informazioni sulle funzioni generiche OAuth 2.0 fornite da Google, consulta OAuth 2.0 e la libreria client OAuth di Google per Java.

Riepilogo: per accedere ai dati protetti archiviati nei servizi Google, utilizza OAuth 2.0 per l'autorizzazione. Le API di Google supportano i flussi OAuth 2.0 per diversi tipi di applicazioni client. In tutti questi flussi, l'applicazione client richiede un token di accesso associato solo all'applicazione client e al proprietario dei dati protetti a cui l'utente ha accesso. Il token di accesso è inoltre associato a un ambito limitato che definisce il tipo di dati a cui ha accesso l'applicazione client, ad esempio "Gestisci le tue attività". Un obiettivo importante di OAuth 2.0 è fornire un accesso pratico e sicuro ai dati protetti, riducendo al minimo il potenziale impatto in caso di furto di un token di accesso.

I pacchetti OAuth 2.0 nella libreria client delle API di Google per Java sono basati sulla libreria client OAuth 2.0 di Google per Java per uso generico.

Per maggiori dettagli, consulta la documentazione Javadoc per i pacchetti seguenti:

console API di Google

Prima di poter accedere alle API di Google, devi configurare un progetto nella console API di Google per l'autenticazione e la fatturazione, indipendentemente dal fatto che il client sia un'applicazione installata, un'applicazione mobile, un server web o un client eseguito nel browser.

Per istruzioni sulla corretta configurazione delle credenziali, consulta la guida della console API.

Credenziale

GoogleCredential

GoogleCredential è una classe helper con protezione dei thread per OAuth 2.0 che consente di accedere alle risorse protette utilizzando un token di accesso. Ad esempio, se hai già un token di accesso, puoi effettuare una richiesta nel seguente modo:

GoogleCredential credential = new GoogleCredential().setAccessToken(accessToken);
Plus plus = new Plus.builder(new NetHttpTransport(),
                             GsonFactory.getDefaultInstance(),
                             credential)
    .setApplicationName("Google-PlusSample/1.0")
    .build();

Identità Google App Engine

Questa credenziale alternativa è basata sull'API Java per l'identità delle app di Google App Engine. A differenza delle credenziali in cui un'applicazione client richiede l'accesso ai dati di un utente finale, l'API App Identity fornisce l'accesso ai dati dell'applicazione client.

Utilizza AppIdentityCredential (da google-api-client-appengine). Questa credenziale è molto più semplice perché Google App Engine si occupa di tutti i dettagli. Devi specificare solo l'ambito OAuth 2.0 necessario.

Codice di esempio tratto da urlshortener-robots-appengine-sample:

static Urlshortener newUrlshortener() {
  AppIdentityCredential credential =
      new AppIdentityCredential(
          Collections.singletonList(UrlshortenerScopes.URLSHORTENER));
  return new Urlshortener.Builder(new UrlFetchTransport(),
                                  GsonFactory.getDefaultInstance(),
                                  credential)
      .build();
}

Datastore

In genere, un token di accesso ha una data di scadenza di un'ora, dopodiché riceverai un errore se tenti di utilizzarlo. GoogleCredential si occupa di "aggiornare" automaticamente il token, il che significa semplicemente ottenere un nuovo token di accesso. A questo scopo, utilizza un token di aggiornamento di lunga durata, che in genere viene ricevuto insieme al token di accesso se utilizzi il parametro access_type=offline durante il flusso del codice di autorizzazione (consulta la pagina GoogleAuthorizationCodeFlow.Builder.setAccessType(String)).

La maggior parte delle applicazioni deve mantenere il token di accesso e/o il token di aggiornamento della credenziale. Per mantenere i token di accesso e/o di aggiornamento delle credenziali, puoi fornire la tua implementazione di DataStoreFactory con StoredCredential; in alternativa, puoi utilizzare una delle seguenti implementazioni fornite dalla libreria:

AppEngine Users:AppEngineCredentialStore è deprecato e verrà rimosso a breve. Ti consigliamo di utilizzare AppEngineDataStoreFactory con StoredCredential. Se hai già memorizzato le credenziali, puoi utilizzare i metodi helper aggiunti migrateTo(AppEngineDataStoreFactory) o migrateTo(DataStore) per eseguire la migrazione.

Puoi utilizzare DataStoreCredentialRefreshListener e impostarlo per la credenziale utilizzando GoogleCredential.Builder.addRefreshListener(CredentialRefreshListener).

Flusso del codice di autorizzazione

Utilizza il flusso del codice di autorizzazione per consentire all'utente finale di concedere alla tua applicazione l'accesso ai propri dati protetti nelle API di Google. Il protocollo per questo flusso è specificato in Concessione del codice di autorizzazione.

Questo flusso viene implementato utilizzando GoogleAuthorizationCodeFlow. I passaggi sono:

In alternativa, se non utilizzi GoogleAuthorizationCodeFlow, puoi utilizzare le classi di livello inferiore:

Quando configuri il progetto nella console API di Google, puoi scegliere tra diverse credenziali, a seconda del flusso che stai utilizzando. Per maggiori dettagli, consulta Configurazione di OAuth 2.0 e scenari OAuth 2.0. Di seguito sono riportati snippet di codice per ciascuno dei flussi.

Applicazioni server web

Il protocollo per questo flusso è spiegato in Utilizzare OAuth 2.0 per applicazioni server web.

Questa libreria fornisce classi helper servlet che semplificano notevolmente il flusso del codice di autorizzazione per i casi d'uso di base. È sufficiente fornire sottoclassi concrete di AbstractAuthorizationCodeServlet e AbstractAuthorizationCodeCallbackServlet (da google-oauth-client-servlet) e aggiungerle al file web.xml. Tieni presente che devi comunque occuparti dell'accesso utente per l'applicazione web ed estrarre un ID utente.

public class CalendarServletSample extends AbstractAuthorizationCodeServlet {

  @Override
  protected void doGet(HttpServletRequest request, HttpServletResponse response)
      throws IOException {
    // do stuff
  }

  @Override
  protected String getRedirectUri(HttpServletRequest req) throws ServletException, IOException {
    GenericUrl url = new GenericUrl(req.getRequestURL().toString());
    url.setRawPath("/oauth2callback");
    return url.build();
  }

  @Override
  protected AuthorizationCodeFlow initializeFlow() throws IOException {
    return new GoogleAuthorizationCodeFlow.Builder(
        new NetHttpTransport(), GsonFactory.getDefaultInstance(),
        "[[ENTER YOUR CLIENT ID]]", "[[ENTER YOUR CLIENT SECRET]]",
        Collections.singleton(CalendarScopes.CALENDAR)).setDataStoreFactory(
        DATA_STORE_FACTORY).setAccessType("offline").build();
  }

  @Override
  protected String getUserId(HttpServletRequest req) throws ServletException, IOException {
    // return user ID
  }
}

public class CalendarServletCallbackSample extends AbstractAuthorizationCodeCallbackServlet {

  @Override
  protected void onSuccess(HttpServletRequest req, HttpServletResponse resp, Credential credential)
      throws ServletException, IOException {
    resp.sendRedirect("/");
  }

  @Override
  protected void onError(
      HttpServletRequest req, HttpServletResponse resp, AuthorizationCodeResponseUrl errorResponse)
      throws ServletException, IOException {
    // handle error
  }

  @Override
  protected String getRedirectUri(HttpServletRequest req) throws ServletException, IOException {
    GenericUrl url = new GenericUrl(req.getRequestURL().toString());
    url.setRawPath("/oauth2callback");
    return url.build();
  }

  @Override
  protected AuthorizationCodeFlow initializeFlow() throws IOException {
    return new GoogleAuthorizationCodeFlow.Builder(
        new NetHttpTransport(), GsonFactory.getDefaultInstance()
        "[[ENTER YOUR CLIENT ID]]", "[[ENTER YOUR CLIENT SECRET]]",
        Collections.singleton(CalendarScopes.CALENDAR)).setDataStoreFactory(
        DATA_STORE_FACTORY).setAccessType("offline").build();
  }

  @Override
  protected String getUserId(HttpServletRequest req) throws ServletException, IOException {
    // return user ID
  }
}

Applicazioni di Google App Engine

Il flusso del codice di autorizzazione su App Engine è quasi identico al flusso del codice di autorizzazione del servlet, con la differenza che possiamo utilizzare l'API Java degli utenti di Google App Engine. L'utente deve aver eseguito l'accesso per abilitare l'API Java Users. Per informazioni su come reindirizzare gli utenti a una pagina di accesso se non l'hanno già fatto, consulta Sicurezza e autenticazione (in web.xml).

La differenza principale rispetto al caso servlet è che vengono fornite sottoclassi concrete di AbstractAppEngineAuthorizationCodeServlet e AbstractAppEngineAuthorizationCodeCallbackServlet (da google-oauth-client-appengine. Estendono le classi servlet astratte e implementano automaticamente il metodo getUserId utilizzando l'API Users Java. AppEngineDataStoreFactory (da google-http-client-appengine) è una buona opzione per mantenere la credenziale utilizzando l'API Data Store di Google App Engine.

Esempio tratto (leggermente modificato) da calendar-appengine-sample:

public class CalendarAppEngineSample extends AbstractAppEngineAuthorizationCodeServlet {

  @Override
  protected void doGet(HttpServletRequest request, HttpServletResponse response)
      throws IOException {
    // do stuff
  }

  @Override
  protected String getRedirectUri(HttpServletRequest req) throws ServletException, IOException {
    return Utils.getRedirectUri(req);
  }

  @Override
  protected AuthorizationCodeFlow initializeFlow() throws IOException {
    return Utils.newFlow();
  }
}

class Utils {
  static String getRedirectUri(HttpServletRequest req) {
    GenericUrl url = new GenericUrl(req.getRequestURL().toString());
    url.setRawPath("/oauth2callback");
    return url.build();
  }

  static GoogleAuthorizationCodeFlow newFlow() throws IOException {
    return new GoogleAuthorizationCodeFlow.Builder(HTTP_TRANSPORT, JSON_FACTORY,
        getClientCredential(), Collections.singleton(CalendarScopes.CALENDAR)).setDataStoreFactory(
        DATA_STORE_FACTORY).setAccessType("offline").build();
  }
}

public class OAuth2Callback extends AbstractAppEngineAuthorizationCodeCallbackServlet {

  private static final long serialVersionUID = 1L;

  @Override
  protected void onSuccess(HttpServletRequest req, HttpServletResponse resp, Credential credential)
      throws ServletException, IOException {
    resp.sendRedirect("/");
  }

  @Override
  protected void onError(
      HttpServletRequest req, HttpServletResponse resp, AuthorizationCodeResponseUrl errorResponse)
      throws ServletException, IOException {
    String nickname = UserServiceFactory.getUserService().getCurrentUser().getNickname();
    resp.getWriter().print("<h3>" + nickname + ", why don't you want to play with me?</h1>");
    resp.setStatus(200);
    resp.addHeader("Content-Type", "text/html");
  }

  @Override
  protected String getRedirectUri(HttpServletRequest req) throws ServletException, IOException {
    return Utils.getRedirectUri(req);
  }

  @Override
  protected AuthorizationCodeFlow initializeFlow() throws IOException {
    return Utils.newFlow();
  }
}

Per un esempio aggiuntivo, vedi storage-serviceaccount-appengine-sample.

Account di servizio

GoogleCredential supporta anche gli account di servizio. A differenza delle credenziali in cui un'applicazione client richiede l'accesso ai dati di un utente finale, gli account di servizio consentono di accedere ai dati dell'applicazione client. L'applicazione client firma la richiesta di un token di accesso utilizzando una chiave privata scaricata dalla console API di Google.

Codice di esempio tratto da plus-serviceaccount-cmdline-sample:

HttpTransport httpTransport = GoogleNetHttpTransport.newTrustedTransport();
JsonFactory jsonFactory = GsonFactory.getDefaultInstance();
...
// Build service account credential.

GoogleCredential credential = GoogleCredential.fromStream(new FileInputStream("MyProject-1234.json"))
    .createScoped(Collections.singleton(PlusScopes.PLUS_ME));
// Set up global Plus instance.
plus = new Plus.Builder(httpTransport, jsonFactory, credential)
    .setApplicationName(APPLICATION_NAME).build();
...

Per un esempio aggiuntivo, consulta storage-serviceaccount-cmdline-sample.

Furto d'identità

Puoi utilizzare il flusso dell'account di servizio anche per impersonare un utente in un dominio di tua proprietà. È molto simile al flusso dell'account di servizio sopra, ma chiami anche GoogleCredential.Builder.setServiceAccountUser(String).

Applicazioni installate

Questo è il flusso del codice di autorizzazione della riga di comando descritto in Utilizzare OAuth 2.0 per le applicazioni installate.

Snippet di esempio da plus-cmdline-sample:

public static void main(String[] args) {
  try {
    httpTransport = GoogleNetHttpTransport.newTrustedTransport();
    dataStoreFactory = new FileDataStoreFactory(DATA_STORE_DIR);
    // authorization
    Credential credential = authorize();
    // set up global Plus instance
    plus = new Plus.Builder(httpTransport, JSON_FACTORY, credential).setApplicationName(
        APPLICATION_NAME).build();
   // ...
}

private static Credential authorize() throws Exception {
  // load client secrets
  GoogleClientSecrets clientSecrets = GoogleClientSecrets.load(JSON_FACTORY,
      new InputStreamReader(PlusSample.class.getResourceAsStream("/client_secrets.json")));
  // set up authorization code flow
  GoogleAuthorizationCodeFlow flow = new GoogleAuthorizationCodeFlow.Builder(
      httpTransport, JSON_FACTORY, clientSecrets,
      Collections.singleton(PlusScopes.PLUS_ME)).setDataStoreFactory(
      dataStoreFactory).build();
  // authorize
  return new AuthorizationCodeInstalledApp(flow, new LocalServerReceiver()).authorize("user");
}

Applicazioni lato client

Per utilizzare il flusso client basato su browser descritto in Utilizzo di OAuth 2.0 per applicazioni lato client, in genere è possibile seguire questi passaggi:

  1. Reindirizza l'utente finale nel browser alla pagina di autorizzazione utilizzando GoogleBrowserClientRequestUrl per concedere all'applicazione del browser l'accesso ai dati protetti dell'utente finale.
  2. Utilizza la libreria client delle API di Google per JavaScript per elaborare il token di accesso trovato nel frammento di URL all'URI di reindirizzamento registrato nella console API di Google.

Esempio di utilizzo di un'applicazione web:

public void doGet(HttpServletRequest request, HttpServletResponse response)throws IOException {
  String url = new GoogleBrowserClientRequestUrl("812741506391.apps.googleusercontent.com",
      "https://oauth2.example.com/oauthcallback", Arrays.asList(
          "https://www.googleapis.com/auth/userinfo.email",
          "https://www.googleapis.com/auth/userinfo.profile")).setState("/profile").build();
  response.sendRedirect(url);
}

Android

@Beta

Quale raccolta utilizzare con Android:

Se stai sviluppando per Android e l'API di Google che vuoi utilizzare è inclusa nella libreria Google Play Services, utilizzala per ottenere prestazioni ed esperienza ottimali. Se l'API di Google che vuoi utilizzare con Android non fa parte della libreria Google Play Services, puoi utilizzare la libreria client dell'API di Google per Java, che supporta Android 4.0 (Ice Cream Sandwich) (o versioni successive), descritta di seguito. Il supporto per Android nella libreria client delle API di Google per Java è @Beta.

Informazioni di base:

A partire da Eclair (SDK 2.1), gli account utente vengono gestiti su un dispositivo Android utilizzando l'account manager. Tutte le autorizzazioni delle app Android sono gestite centralmente dall'SDK tramite AccountManager. Specifica l'ambito OAuth 2.0 necessario per la tua applicazione e l'applicazione restituisce un token di accesso da utilizzare.

L'ambito OAuth 2.0 viene specificato tramite il parametro authTokenType come oauth2: più l'ambito. Ad esempio:

oauth2:https://www.googleapis.com/auth/tasks

Specifica l'accesso in lettura/scrittura all'API Google Tasks. Se hai bisogno di più ambiti OAuth 2.0, utilizza un elenco separato da spazi.

Alcune API hanno parametri authTokenType speciali che funzionano. Ad esempio, "Gestisci le tue attività" è un alias per l'esempio authtokenType mostrato sopra.

Devi inoltre specificare la chiave API dalla console API di Google. In caso contrario, il token fornito da AccountManager ti fornisce solo una quota anonima, che di solito è molto bassa. Al contrario, se specifichi una chiave API, ricevi una quota senza costi più elevata e puoi facoltativamente impostare la fatturazione per l'utilizzo oltre tale quota.

Snippet di codice di esempio tratto da tasks-android-sample:

com.google.api.services.tasks.Tasks service;

@Override
public void onCreate(Bundle savedInstanceState) {
  credential =
      GoogleAccountCredential.usingOAuth2(this, Collections.singleton(TasksScopes.TASKS));
  SharedPreferences settings = getPreferences(Context.MODE_PRIVATE);
  credential.setSelectedAccountName(settings.getString(PREF_ACCOUNT_NAME, null));
  service =
      new com.google.api.services.tasks.Tasks.Builder(httpTransport, jsonFactory, credential)
          .setApplicationName("Google-TasksAndroidSample/1.0").build();
}

private void chooseAccount() {
  startActivityForResult(credential.newChooseAccountIntent(), REQUEST_ACCOUNT_PICKER);
}

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
  super.onActivityResult(requestCode, resultCode, data);
  switch (requestCode) {
    case REQUEST_GOOGLE_PLAY_SERVICES:
      if (resultCode == Activity.RESULT_OK) {
        haveGooglePlayServices();
      } else {
        checkGooglePlayServicesAvailable();
      }
      break;
    case REQUEST_AUTHORIZATION:
      if (resultCode == Activity.RESULT_OK) {
        AsyncLoadTasks.run(this);
      } else {
        chooseAccount();
      }
      break;
    case REQUEST_ACCOUNT_PICKER:
      if (resultCode == Activity.RESULT_OK && data != null && data.getExtras() != null) {
        String accountName = data.getExtras().getString(AccountManager.KEY_ACCOUNT_NAME);
        if (accountName != null) {
          credential.setSelectedAccountName(accountName);
          SharedPreferences settings = getPreferences(Context.MODE_PRIVATE);
          SharedPreferences.Editor editor = settings.edit();
          editor.putString(PREF_ACCOUNT_NAME, accountName);
          editor.commit();
          AsyncLoadTasks.run(this);
        }
      }
      break;
  }
}