OAuth 2.0 e libreria client OAuth di Google per Java

Panoramica

Scopo: questo documento descrive le funzioni generiche OAuth 2.0 offerte dalla libreria client OAuth di Google per Java. Puoi utilizzare queste funzioni per l'autenticazione e l'autorizzazione di qualsiasi servizio Internet.

Per istruzioni sull'utilizzo di GoogleCredential per eseguire l'autorizzazione OAuth 2.0 con i servizi Google, consulta Utilizzo di OAuth 2.0 con la libreria client dell'API di Google per Java.

Riepilogo: OAuth 2.0 è una specifica standard che consente agli utenti finali di autorizzare in modo sicuro un'applicazione client ad accedere alle risorse lato server protette. Inoltre, la specifica del token di connessione OAuth 2.0 spiega come accedere alle risorse protette utilizzando un token di accesso concesso durante il processo di autorizzazione dell'utente finale.

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

Registrazione client

Prima di utilizzare la libreria client OAuth di Google per Java, è probabile che tu debba registrare l'applicazione con un server di autorizzazione per ricevere un ID client e un client secret. Per informazioni generali su questa procedura, consulta la specifica di registrazione dei client.

Archivio di credenziali e credenziali

Credenziale è una classe helper OAuth 2.0 con protezione dei thread per l'accesso alle risorse protette mediante un token di accesso. Quando utilizzi un token di aggiornamento, Credential aggiorna il token di accesso anche quando quest'ultimo scade utilizzando quello di aggiornamento. Ad esempio, se hai già un token di accesso, puoi effettuare una richiesta nel seguente modo:

  public static HttpResponse executeGet(
      HttpTransport transport, JsonFactory jsonFactory, String accessToken, GenericUrl url)
      throws IOException {
    Credential credential =
        new Credential(BearerToken.authorizationHeaderAccessMethod()).setAccessToken(accessToken);
    HttpRequestFactory requestFactory = transport.createRequestFactory(credential);
    return requestFactory.buildGetRequest(url).execute();
  }

La maggior parte delle applicazioni deve conservare il token di accesso e il token di aggiornamento della credenziale per evitare un reindirizzamento futuro alla pagina di autorizzazione nel browser. L'implementazione di CredentialStore in questa libreria è deprecata e verrà rimossa nelle release future. In alternativa, utilizza le interfacce DataStoreFactory e DataStore con StoredCredential, fornite dalla libreria client HTTP di Google per Java.

Puoi utilizzare una delle seguenti implementazioni fornite dalla libreria:

Utenti di Google App Engine:

L'API AppEngineCredentialStore è deprecata e verrà rimossa.

Ti consigliamo di utilizzare AppEngineDataStoreFactory con StoredCredential. Se hai memorizzato le credenziali in precedenza, puoi utilizzare i metodi helper aggiunti migrateTo(AppEngineDataStoreFactory) o migrateTo(DataStore) per eseguire la migrazione.

Utilizza DataStoreCredentialRefreshListener e impostalo 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 suoi dati protetti. Il protocollo per questo flusso è specificato nella specifica per la concessione del codice di autorizzazione.

Questo flusso viene implementato utilizzando AuthorizationCodeFlow. I passaggi sono:

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

Flusso del codice di autorizzazione servit

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

Codice di esempio:

public class ServletSample 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 AuthorizationCodeFlow.Builder(BearerToken.authorizationHeaderAccessMethod(),
        new NetHttpTransport(),
        new JacksonFactory(),
        new GenericUrl("https://server.example.com/token"),
        new BasicAuthentication("s6BhdRkqt3", "7Fjfp0ZBr1KtDRbnfVdmIw"),
        "s6BhdRkqt3",
        "https://server.example.com/authorize").setCredentialDataStore(
            StoredCredential.getDefaultDataStore(
                new FileDataStoreFactory(new File("datastoredir"))))
        .build();
  }

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

public class ServletCallbackSample 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 AuthorizationCodeFlow.Builder(BearerToken.authorizationHeaderAccessMethod(),
        new NetHttpTransport(),
        new JacksonFactory(),
        new GenericUrl("https://server.example.com/token"),
        new BasicAuthentication("s6BhdRkqt3", "7Fjfp0ZBr1KtDRbnfVdmIw"),
        "s6BhdRkqt3",
        "https://server.example.com/authorize").setCredentialDataStore(
            StoredCredential.getDefaultDataStore(
                new FileDataStoreFactory(new File("datastoredir"))))
        .build();
  }

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

Flusso del codice di autorizzazione 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 attivare 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 fornisci 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 Libreria client HTTP di Google per Java è una buona opzione per mantenere la credenziale utilizzando l'API Data Store di Google App Engine.

Codice di esempio:

public class AppEngineSample extends AbstractAppEngineAuthorizationCodeServlet {

  @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 AuthorizationCodeFlow.Builder(BearerToken.authorizationHeaderAccessMethod(),
        new UrlFetchTransport(),
        new JacksonFactory(),
        new GenericUrl("https://server.example.com/token"),
        new BasicAuthentication("s6BhdRkqt3", "7Fjfp0ZBr1KtDRbnfVdmIw"),
        "s6BhdRkqt3",
        "https://server.example.com/authorize").setCredentialStore(
            StoredCredential.getDefaultDataStore(AppEngineDataStoreFactory.getDefaultInstance()))
        .build();
  }
}

public class AppEngineCallbackSample extends AbstractAppEngineAuthorizationCodeCallbackServlet {

  @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 AuthorizationCodeFlow.Builder(BearerToken.authorizationHeaderAccessMethod(),
        new UrlFetchTransport(),
        new JacksonFactory(),
        new GenericUrl("https://server.example.com/token"),
        new BasicAuthentication("s6BhdRkqt3", "7Fjfp0ZBr1KtDRbnfVdmIw"),
        "s6BhdRkqt3",
        "https://server.example.com/authorize").setCredentialStore(
            StoredCredential.getDefaultDataStore(AppEngineDataStoreFactory.getDefaultInstance()))
        .build();
  }
}

Flusso del codice di autorizzazione della riga di comando

Codice di esempio semplificato tratto da dailymotion-cmdline-sample:

/** Authorizes the installed application to access user's protected data. */
private static Credential authorize() throws Exception {
  OAuth2ClientCredentials.errorIfNotSpecified();
  // set up authorization code flow
  AuthorizationCodeFlow flow = new AuthorizationCodeFlow.Builder(BearerToken
      .authorizationHeaderAccessMethod(),
      HTTP_TRANSPORT,
      JSON_FACTORY,
      new GenericUrl(TOKEN_SERVER_URL),
      new ClientParametersAuthentication(
          OAuth2ClientCredentials.API_KEY, OAuth2ClientCredentials.API_SECRET),
      OAuth2ClientCredentials.API_KEY,
      AUTHORIZATION_SERVER_URL).setScopes(Arrays.asList(SCOPE))
      .setDataStoreFactory(DATA_STORE_FACTORY).build();
  // authorize
  LocalServerReceiver receiver = new LocalServerReceiver.Builder().setHost(
      OAuth2ClientCredentials.DOMAIN).setPort(OAuth2ClientCredentials.PORT).build();
  return new AuthorizationCodeInstalledApp(flow, receiver).authorize("user");
}

private static void run(HttpRequestFactory requestFactory) throws IOException {
  DailyMotionUrl url = new DailyMotionUrl("https://api.dailymotion.com/videos/favorites");
  url.setFields("id,tags,title,url");

  HttpRequest request = requestFactory.buildGetRequest(url);
  VideoFeed videoFeed = request.execute().parseAs(VideoFeed.class);
  ...
}

public static void main(String[] args) {
  ...
  DATA_STORE_FACTORY = new FileDataStoreFactory(DATA_STORE_DIR);
  final Credential credential = authorize();
  HttpRequestFactory requestFactory =
      HTTP_TRANSPORT.createRequestFactory(new HttpRequestInitializer() {
        @Override
        public void initialize(HttpRequest request) throws IOException {
          credential.initialize(request);
          request.setParser(new JsonObjectParser(JSON_FACTORY));
        }
      });
  run(requestFactory);
  ...
}

Flusso client basato su browser

Questi sono i passaggi tipici del flusso client basato su browser specificato nella specifica di concessione implicita:

  • Utilizzando BrowserClientRequestUrl, reindirizza il browser dell'utente finale alla pagina di autorizzazione in cui l'utente finale può concedere alla tua applicazione l'accesso ai propri dati protetti.
  • Utilizzare un'applicazione JavaScript per elaborare il token di accesso trovato nel frammento di URL all'URI di reindirizzamento registrato nel server di autorizzazione.

Esempio di utilizzo di un'applicazione web:

public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException {
  String url = new BrowserClientRequestUrl(
      "https://server.example.com/authorize", "s6BhdRkqt3").setState("xyz")
      .setRedirectUri("https://client.example.com/cb").build();
  response.sendRedirect(url);
}

Rilevare un token di accesso scaduto

In base alla specifica di connessione OAuth 2.0, quando il server viene chiamato per accedere a una risorsa protetta con un token di accesso scaduto, in genere il server risponde con un codice di stato HTTP 401 Unauthorized come il seguente:

   HTTP/1.1 401 Unauthorized
   WWW-Authenticate: Bearer realm="example",
                     error="invalid_token",
                     error_description="The access token expired"

Tuttavia, sembra esserci molta flessibilità nella specifica. Per maggiori dettagli, consulta la documentazione del provider OAuth 2.0.

Un approccio alternativo consiste nel controllare il parametro expires_in nella risposta del token di accesso. Specifica la durata in secondi del token di accesso concesso, che in genere è di un'ora. Tuttavia, il token di accesso potrebbe non scadere effettivamente alla fine di questo periodo e il server potrebbe continuare a consentire l'accesso. Ecco perché di solito consigliamo di attendere il codice di stato 401 Unauthorized, anziché presumere che il token sia scaduto in base al tempo trascorso. In alternativa, puoi provare ad aggiornare un token di accesso poco prima che scada e, se il server token non è disponibile, continua a utilizzarlo finché non ricevi un 401. Questa è la strategia utilizzata per impostazione predefinita in Credenziale.

Un'altra opzione è quella di acquisire un nuovo token di accesso prima di ogni richiesta, ma ciò richiede ogni volta una richiesta HTTP aggiuntiva al server dei token, quindi probabilmente è una scelta scadente in termini di velocità e utilizzo della rete. Idealmente, archivia il token di accesso in uno spazio di archiviazione sicuro e permanente per ridurre al minimo le richieste di nuovi token di accesso da parte di un'applicazione. Per le applicazioni installate, tuttavia, l'archiviazione protetta è un problema difficile.

Tieni presente che un token di accesso potrebbe non essere più valido per motivi diversi dalla scadenza, ad esempio se l'utente lo ha revocato in modo esplicito, quindi assicurati che il codice di gestione degli errori sia solido. Una volta rilevato che un token non è più valido, ad esempio se è scaduto o è stato revocato, devi rimuovere il token di accesso dallo spazio di archiviazione. Su Android, ad esempio, devi chiamare AccountManager.invalidateAuthToken.