OAuth 2.0 e libreria client OAuth di Google per Java

Panoramica

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

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

Riepilogo: OAuth 2.0 è un specifica standard per consentire agli utenti finali di autorizzare in modo sicuro un client per accedere a risorse lato server protette. Inoltre, Token di connessione OAuth 2.0 spiega come accedere a tali risorse protette utilizzando un modello di accesso richiesto durante il processo di autorizzazione dell'utente finale.

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

Registrazione client

Prima di utilizzare la libreria client OAuth di Google per Java, probabilmente dovrai registrare l'applicazione con un server di autorizzazione per ricevere un ID client e client secret. Per informazioni generali su questo processo, consulta Cliente specifiche per la registrazione.)

Archivio credenziali e credenziali

Credenziale è una classe helper OAuth 2.0 sicura per i thread che consente di accedere alle risorse protette utilizzando una token di accesso. Quando utilizzi un token di aggiornamento, Credential aggiorna anche l'accesso quando il token di accesso scade utilizzando il token di aggiornamento. Ad esempio, se disponi già di 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 rendere permanente il token di accesso delle credenziali di aggiornamento per evitare un reindirizzamento futuro all'autorizzazione nel browser. La CredentialStore l'implementazione in questa libreria è deprecata e verrà rimossa in futuro release. L'alternativa è utilizzare DataStoreFactory e DataStore si interfaccia con StoredCredential, che vengono forniti 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 nel modo precedente, puoi usare i metodi helper aggiunti migrateTo(AppEngineDataStoreFactory) o migrateTo(DataStore) di cui eseguire la migrazione.

Utilizza DataStoreCredentialRefreshListener e la imposti 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 la tua applicazione l'accesso ai propri dati protetti. Il protocollo per questo flusso è specificato nel 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 servlet

Questa libreria fornisce classi helper servlet per semplificare significativamente del codice di autorizzazione per casi d'uso di base. Devi solo fornire sottoclassi concrete di AbstractAuthorizationCodeServlet e AbstractAuthorizationCodeCallbackServlet (da google-oauth-client-servlet) e aggiungerli al file web.xml. Tieni presente che devi ancora occuparti del cliente per la tua 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 servlet del codice di autorizzazione, con la differenza che possiamo sfruttare API Users Java. Per abilitare l'API Users Java, l'utente deve aver eseguito l'accesso. della informazioni sul reindirizzamento a una pagina di accesso, se non lo sono già se hai eseguito l'accesso, vedi Sicurezza e autenticazione (in web.xml).

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

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 dalla riga di comando

Esempio di codice 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 nel Specifica di concessione implicita:

  • Con BrowserClientRequestUrl, reindirizzare il browser dell'utente finale alla pagina di autorizzazione dove l'utente finale può concedere alla tua applicazione l'accesso ai propri dati protetti.
  • Utilizza un'applicazione JavaScript per elaborare il token di accesso trovato nell'URL all'URI di reindirizzamento registrato presso il server di autorizzazione.

Utilizzo di esempio per 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);
}

Rilevamento di 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 accesso scaduto. il server risponde con un codice di stato HTTP 401 Unauthorized ad esempio:

   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 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, ovvero di solito un'ora. Tuttavia, il token di accesso potrebbe non scadere alla fine di quel periodo e il server potrebbe continuare a consentire l'accesso. Ecco perché in genere consigliamo di attendere il codice di stato 401 Unauthorized, anziché sempre che il token sia scaduto in base al tempo trascorso. In alternativa, puoi prova ad aggiornare un token di accesso poco prima che scada e se il server non è disponibile, continua a utilizzare il token di accesso finché non ricevi un 401. Questo è la strategia utilizzata per impostazione predefinita Credenziale.

Un'altra opzione è ottenere un nuovo token di accesso prima di ogni richiesta, ma richiede una richiesta HTTP aggiuntiva al server token ogni volta, quindi probabilmente scelta scadente in termini di velocità e utilizzo della rete. Idealmente, memorizza il token di accesso nello spazio di archiviazione permanente sicuro per ridurre al minimo le richieste di un nuovo accesso da parte di un'applicazione. di token. Per le applicazioni installate, però, l'archiviazione sicura rappresenta 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 ha revocato il token in modo esplicito, quindi assicurati che per la gestione degli errori è efficace. Una volta rilevato che un token non è più valido, ad esempio se è scaduto o è stato revocato, devi rimuovere l'accesso dal tuo spazio di archiviazione. Su Android, ad esempio, devi chiamare AccountManager.invalidateAuthToken.