OAuth 2.0 und die Google OAuth-Clientbibliothek für Java

Überblick

Zweck:In diesem Dokument werden die allgemeinen OAuth 2.0-Funktionen der Google OAuth-Clientbibliothek für Java beschrieben. Sie können diese Funktionen zur Authentifizierung und Autorisierung für alle Internetdienste verwenden.

Eine Anleitung zur Verwendung von GoogleCredential für die OAuth 2.0-Autorisierung mit Google-Diensten finden Sie unter OAuth 2.0 mit der Google API-Clientbibliothek für Java verwenden.

Zusammenfassung:OAuth 2.0 ist eine Standardspezifikation, mit der Endnutzer eine Clientanwendung sicher für den Zugriff auf geschützte serverseitige Ressourcen autorisieren können. Außerdem wird in der Spezifikation OAuth 2.0-Inhabertokens erläutert, wie auf diese geschützten Ressourcen mithilfe eines Zugriffstokens zugegriffen wird, das während der Endnutzerautorisierung gewährt wird.

Weitere Informationen finden Sie in der Javadoc-Dokumentation für die folgenden Pakete:

Kundenregistrierung

Bevor Sie die Google OAuth-Clientbibliothek für Java verwenden können, müssen Sie Ihre Anwendung möglicherweise bei einem Autorisierungsserver registrieren, um eine Client-ID und einen Clientschlüssel zu erhalten. Allgemeine Informationen zu diesem Vorgang finden Sie in der Spezifikation für die Clientregistrierung.

Anmeldedaten- und Anmeldedatenspeicher

Credential ist eine Thread-sichere OAuth 2.0-Hilfsklasse für den Zugriff auf geschützte Ressourcen mithilfe eines Zugriffstokens. Bei Verwendung eines Aktualisierungstokens aktualisiert Credential das Zugriffstoken auch dann, wenn das Zugriffstoken mithilfe des Aktualisierungstokens abläuft. Wenn Sie beispielsweise bereits ein Zugriffstoken haben, können Sie so eine Anfrage stellen:

  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();
  }

Die meisten Anwendungen müssen das Zugriffstoken und das Aktualisierungstoken der Anmeldedaten dauerhaft speichern, um eine künftige Weiterleitung zur Autorisierungsseite im Browser zu vermeiden. Die Implementierung von CredentialStore in dieser Bibliothek wurde verworfen und wird in zukünftigen Releases entfernt. Alternativ können Sie die Schnittstellen DataStoreFactory und DataStore mit StoredCredential verwenden, die von der Google-HTTP-Clientbibliothek für Java bereitgestellt werden.

Sie können eine der folgenden von der Bibliothek bereitgestellten Implementierungen verwenden:

Google App Engine-Nutzer:

AppEngineCredentialStore wurde verworfen und wird entfernt.

Wir empfehlen die Verwendung von AppEngineDataStoreFactory mit StoredCredential. Wenn Sie Anmeldedaten auf die alte Art und Weise gespeichert haben, können Sie die hinzugefügten Hilfsmethoden migrateTo(AppEngineDataStoreFactory) oder migrateTo(DataStore) für die Migration verwenden.

Verwenden Sie den DataStoreCredentialRefreshListener und legen Sie ihn mit GoogleCredential.Builder.addRefreshListener(CredentialRefreshListener) für die Anmeldedaten fest.

Vorgang mit Autorisierungscode

Verwenden Sie den Vorgang mit Autorisierungscode, damit der Endnutzer Ihrer Anwendung Zugriff auf seine geschützten Daten gewähren kann. Das Protokoll für diesen Ablauf ist in der Spezifikation zur Gewährung von Autorisierungscode angegeben.

Dieser Ablauf wird mithilfe von AuthorizationCodeFlow implementiert. Folgende Schritte sind auszuführen:

Wenn Sie AuthorizationCodeFlow nicht nutzen, können Sie alternativ die niedrigeren Klassen verwenden:

Vorgang mit Servlet-Autorisierungscode

Diese Bibliothek bietet Servlet-Hilfsklassen, um den Vorgang mit Autorisierungscode für grundlegende Anwendungsfälle erheblich zu vereinfachen. Dazu stellen Sie einfach konkrete Unterklassen von AbstractAuthorizationCodeServlet und AbstractAuthorizationCodeCallbackServlet (von google-oauth-client-servlet) bereit und fügen sie der Datei web.xml hinzu. Beachten Sie, dass Sie sich weiterhin um die Nutzeranmeldung für Ihre Webanwendung kümmern und eine Nutzer-ID extrahieren müssen.

Beispielcode:

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
  }
}

Google App Engine-Vorgang mit Autorisierungscode

Der Vorgang mit dem Autorisierungscode in App Engine ist fast identisch mit dem Ablauf mit dem Servlet-Autorisierungscode, mit der Ausnahme, dass wir die Users Java API von Google App Engine nutzen können. Damit die Users Java API aktiviert werden kann, muss der Nutzer angemeldet sein. Informationen zum Weiterleiten von Nutzern zu einer Anmeldeseite, wenn sie noch nicht angemeldet sind, finden Sie unter Sicherheit und Authentifizierung (in web.xml).

Der Hauptunterschied zum Servlet-Fall besteht darin, dass Sie konkrete Unterklassen von AbstractAppEngineAuthorizationCodeServlet und AbstractAppEngineAuthorizationCodeCallbackServlet (von google-oauth-client-appengine) angeben. Sie erweitern die abstrakten Servlet-Klassen und implementieren die Methode getUserId mithilfe der Users Java API. AppEngineDataStoreFactory (aus der Google HTTP-Clientbibliothek für Java) eignet sich gut, um die Anmeldedaten mit der Google App Engine Data Store API zu speichern.

Beispielcode:

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();
  }
}

Ablauf mit Autorisierungscode über die Befehlszeile

Vereinfachter Beispielcode aus 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);
  ...
}

Browserbasierter Clientfluss

Dies sind die typischen Schritte des browserbasierten Clientablaufs, der in der Spezifikation für implizite Erteilung angegeben ist:

  • Leiten Sie den Browser des Endnutzers mit BrowserClientRequestUrl auf die Autorisierungsseite weiter, auf der dieser Ihrer Anwendung Zugriff auf seine geschützten Daten gewähren kann.
  • Verwenden Sie eine JavaScript-Anwendung, um das Zugriffstoken zu verarbeiten, das im URL-Fragment des Weiterleitungs-URI vorhanden ist, der beim Autorisierungsserver registriert ist.

Verwendungsbeispiel für eine Webanwendung:

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);
}

Abgelaufenes Zugriffstoken erkennen

Wenn der Server für den Zugriff auf eine geschützte Ressource mit einem abgelaufenen Zugriffstoken aufgerufen wird, antwortet der Server gemäß der OAuth 2.0-Inhaberspezifikation in der Regel mit einem HTTP-Statuscode 401 Unauthorized wie dem folgenden:

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

Die Spezifikation scheint jedoch sehr flexibel zu sein. Weitere Informationen finden Sie in der Dokumentation des OAuth 2.0-Anbieters.

Alternativ können Sie den Parameter expires_in in der Antwort des Zugriffstokens prüfen. Damit wird die Lebensdauer des gewährten Zugriffstokens in Sekunden angegeben, in der Regel eine Stunde. Es ist jedoch möglich, dass das Zugriffstoken am Ende dieses Zeitraums nicht wirklich abläuft und der Server weiterhin Zugriff gewährt. Deshalb empfehlen wir in der Regel, auf den Statuscode 401 Unauthorized zu warten, anstatt davon auszugehen, dass das Token auf der Grundlage der verstrichenen Zeit abgelaufen ist. Alternativ können Sie versuchen, ein Zugriffstoken kurz vor seinem Ablauf zu aktualisieren. Wenn der Tokenserver nicht verfügbar ist, können Sie das Zugriffstoken so lange verwenden, bis Sie ein 401 erhalten. Diese Strategie wird standardmäßig in Anmeldedaten verwendet.

Eine weitere Möglichkeit besteht darin, vor jeder Anfrage ein neues Zugriffstoken abzurufen. Dazu ist jedoch jedes Mal eine zusätzliche HTTP-Anfrage an den Tokenserver erforderlich. In Bezug auf Geschwindigkeit und Netzwerknutzung ist dies wahrscheinlich eine schlechte Wahl. Idealerweise sollten Zugriffstokens in einem sicheren, dauerhaften Speicher gespeichert werden, um die Anzahl der Anfragen einer Anwendung für neue Zugriffstokens zu minimieren. Bei installierten Anwendungen ist die sichere Speicherung jedoch ein schwieriges Problem.

Ein Zugriffstoken kann auch aus anderen Gründen als seinem Ablauf ungültig werden, z. B. weil der Nutzer das Token explizit widerrufen hat. Daher sollten Sie darauf achten, dass der Code zur Fehlerbehandlung robust ist. Sobald Sie feststellen, dass ein Token nicht mehr gültig ist, z. B. weil es abgelaufen ist oder widerrufen wurde, müssen Sie das Zugriffstoken aus dem Speicher entfernen. Unter Android müssen Sie beispielsweise AccountManager.invalidateAuthToken aufrufen.