Używanie protokołu OAuth 2.0 z biblioteką klienta interfejsów API Google dla języka Java

Przegląd

Cel: w tym dokumencie wyjaśniamy, jak używać klasy narzędziowej GoogleCredential do autoryzacji OAuth 2.0 w usługach Google. Informacje o ogólnych funkcjach OAuth 2.0, które udostępniamy, znajdziesz w artykule OAuth 2.0 i biblioteka klienta Google OAuth dla języka Java.

Podsumowanie: aby uzyskać dostęp do chronionych danych przechowywanych w usługach Google, używaj OAuth 2.0 do autoryzacji. Interfejsy API Google obsługują procesy OAuth 2.0 w przypadku różnych typów aplikacji klienckich. We wszystkich tych procesach aplikacja kliencka żąda tokena dostępu, który jest powiązany tylko z nią i właścicielem chronionych danych, do których uzyskuje dostęp. Token dostępu jest też powiązany z ograniczonym zakresem, który określa rodzaj danych, do których ma dostęp aplikacja kliencka (np. „Zarządzaj swoimi zadaniami”). Ważnym celem protokołu OAuth 2.0 jest zapewnienie bezpiecznego i wygodnego dostępu do chronionych danych przy jednoczesnym zminimalizowaniu potencjalnych skutków kradzieży tokena dostępu.

Pakiety OAuth 2.0 w bibliotece klienta interfejsu API Google dla języka Java są oparte na ogólnej bibliotece klienta Google OAuth 2.0 dla języka Java.

Szczegółowe informacje znajdziesz w dokumentacji Javadoc tych pakietów:

Konsola interfejsów API Google

Zanim uzyskasz dostęp do interfejsów API Google, musisz skonfigurować projekt w Konsoli interfejsów API Google na potrzeby autoryzacji i płatności, niezależnie od tego, czy Twój klient jest zainstalowaną aplikacją, aplikacją mobilną, serwerem internetowym czy klientem działającym w przeglądarce.

Instrukcje prawidłowego konfigurowania danych logowania znajdziesz w Pomocy Konsoli interfejsów API.

Dane logowania

GoogleCredential

GoogleCredential to bezpieczna wątkowo klasa pomocnicza protokołu OAuth 2.0, która umożliwia dostęp do chronionych zasobów za pomocą tokena dostępu. Jeśli na przykład masz już token dostępu, możesz wysłać żądanie w ten sposób:

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

Tożsamość Google App Engine

Te alternatywne dane logowania są oparte na interfejsie Google App Engine App Identity Java API. W przeciwieństwie do danych logowania, w przypadku których aplikacja kliencka prosi o dostęp do danych użytkownika, interfejs App Identity API zapewnia dostęp do danych samej aplikacji klienckiej.

Użyj AppIdentityCredential (z pakietu google-api-client-appengine). Ten rodzaj danych logowania jest znacznie prostszy, ponieważ Google App Engine zajmuje się wszystkimi szczegółami. Określasz tylko potrzebny zakres OAuth 2.0.

Przykładowy kod pochodzący z 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();
}

Magazyn danych

Token dostępu zwykle wygasa po godzinie. Jeśli spróbujesz go użyć po tym czasie, pojawi się błąd. Klasa GoogleCredential automatycznie „odświeża” token, co oznacza po prostu pobieranie nowego tokena dostępu. Odbywa się to za pomocą tokena odświeżania o długim okresie ważności, który jest zwykle otrzymywany wraz z tokenem dostępu, jeśli podczas przepływu kodu autoryzacji używasz parametru access_type=offline (patrz GoogleAuthorizationCodeFlow.Builder.setAccessType(String)).

Większość aplikacji będzie musiała przechowywać token dostępu lub token odświeżania danych logowania. Aby zachować tokeny dostępu lub odświeżania danych logowania, możesz podać własną implementację interfejsu DataStoreFactory z interfejsem StoredCredential lub użyć jednej z tych implementacji udostępnianych przez bibliotekę:

Użytkownicy App Engine: AppEngineCredentialStore został wycofany i wkrótce zostanie usunięty. Zalecamy używanie AppEngineDataStoreFactoryStoredCredential. Jeśli masz dane logowania zapisane w starym formacie, możesz użyć dodatkowych metod pomocniczych migrateTo(AppEngineDataStoreFactory) lub migrateTo(DataStore), aby przeprowadzić migrację.

Możesz użyć interfejsu DataStoreCredentialRefreshListener i ustawić go dla danych logowania za pomocą metody GoogleCredential.Builder.addRefreshListener(CredentialRefreshListener).

Przepływ kodu autoryzacji

Użyj procesu kodu autoryzacji, aby umożliwić użytkownikowi końcowemu przyznanie aplikacji dostępu do chronionych danych w interfejsach API Google. Protokół tego procesu jest określony w sekcji Authorization Code Grant (Uwierzytelnianie za pomocą kodu autoryzacji).

Ten przepływ jest realizowany za pomocą klasy GoogleAuthorizationCodeFlow. Kroki:

Jeśli nie używasz GoogleAuthorizationCodeFlow, możesz użyć klas niższego poziomu:

Podczas konfigurowania projektu w Konsoli interfejsów API Google wybierasz różne dane logowania w zależności od używanego procesu. Więcej informacji znajdziesz w artykułach Konfigurowanie OAuth 2.0Scenariusze OAuth 2.0. Poniżej znajdziesz fragmenty kodu dla każdego z tych procesów.

Aplikacje serwera WWW

Protokół tego procesu jest opisany w artykule Używanie OAuth 2.0 w aplikacjach udostępnianych przez serwer WWW.

Ta biblioteka zawiera klasy pomocnicze servletów, które znacznie upraszczają przepływ kodu autoryzacji w przypadku podstawowych zastosowań. Wystarczy, że podasz konkretne podklasy AbstractAuthorizationCodeServletAbstractAuthorizationCodeCallbackServlet (z google-oauth-client-servlet) i dodasz je do pliku web.xml. Pamiętaj, że nadal musisz zadbać o logowanie użytkowników w aplikacji internetowej i wyodrębnianie identyfikatora użytkownika.

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

aplikacje Google App Engine,

Przepływ kodu autoryzacji w App Engine jest niemal identyczny z przepływem kodu autoryzacji w serwlecie, z tym wyjątkiem, że możemy korzystać z interfejsu Users Java API Google App Engine. Aby włączyć interfejs Users Java API, użytkownik musi być zalogowany. Informacje o przekierowywaniu użytkowników na stronę logowania, jeśli nie są jeszcze zalogowani, znajdziesz w sekcji Security and Authentication (w pliku web.xml).

Główna różnica w porównaniu z servletami polega na tym, że udostępniasz konkretne podklasy AbstractAppEngineAuthorizationCodeServletAbstractAppEngineAuthorizationCodeCallbackServlet (z google-oauth-client-appengine). Rozszerzają one abstrakcyjne klasy servletów i implementują za Ciebie metodę getUserId za pomocą interfejsu Users Java API. AppEngineDataStoreFactory (z google-http-client-appengine) to dobra opcja do utrwalania danych logowania za pomocą interfejsu Google App Engine Data Store API.

Przykład zaczerpnięty (nieznacznie zmodyfikowany) z 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();
  }
}

Dodatkowy przykład znajdziesz w artykule storage-serviceaccount-appengine-sample.

Konta usługi

GoogleCredential obsługuje też konta usługi. W przeciwieństwie do danych logowania, za pomocą których aplikacja kliencka żąda dostępu do danych użytkownika, konta usługi zapewniają dostęp do danych samej aplikacji klienckiej. Aplikacja kliencka podpisuje żądanie tokena dostępu za pomocą klucza prywatnego pobranego z konsoli interfejsów API Google.

Przykład użycia:

HttpTransport httpTransport = new NetHttpTransport();
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();
...

Dodatkowy przykład znajdziesz w artykule storage-serviceaccount-cmdline-sample.

Podszywanie się pod inne osoby

Możesz też użyć przepływu konta usługi, aby podszywać się pod użytkownika w domenie, która należy do Ciebie. Jest to bardzo podobne do procesu konta usługi opisanego powyżej, ale dodatkowo wywołujesz funkcję GoogleCredential.Builder.setServiceAccountUser(String).

Zainstalowane aplikacje

Jest to przepływ kodu autoryzacji wiersza poleceń opisany w artykule Używanie OAuth 2.0 w przypadku zainstalowanych aplikacji.

Przykład użycia:

public static void main(String[] args) {
  try {
    httpTransport = new NetHttpTransport();
    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");
}

Aplikacje po stronie klienta

Aby użyć przepływu klienta opartego na przeglądarce opisanego w artykule Korzystanie z OAuth 2.0 w przypadku aplikacji działających po stronie klienta, zwykle wykonujesz te czynności:

  1. Przekieruj użytkownika w przeglądarce na stronę autoryzacji za pomocą adresu GoogleBrowserClientRequestUrl, aby przyznać aplikacji przeglądarki dostęp do chronionych danych użytkownika.
  2. Użyj biblioteki klienta interfejsu API Google dla JavaScriptu, aby przetworzyć token dostępu znaleziony we fragmencie adresu URL w adresie URI przekierowania zarejestrowanym w Konsoli interfejsów API Google.

Przykładowe użycie w aplikacji internetowej:

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

Której biblioteki używać na Androidzie:

Jeśli tworzysz aplikację na Androida, a interfejs API Google, którego chcesz użyć, jest zawarty w bibliotece Usług Google Play, użyj tej biblioteki, aby uzyskać najlepszą wydajność i najlepsze wrażenia. Jeśli interfejs API Google, którego chcesz używać na Androidzie, nie jest częścią biblioteki Usług Google Play, możesz użyć biblioteki klienta interfejsu API Google dla języka Java, która obsługuje Androida 4.0 (Ice Cream Sandwich) lub nowszego. Opis tej biblioteki znajdziesz tutaj. Obsługa Androida w bibliotece klienta interfejsu API Google dla języka Java jest w wersji @Beta.

Informacje ogólne:

Od wersji Eclair (SDK 2.1) konta użytkowników są zarządzane na urządzeniu z Androidem za pomocą Menedżera kont. Cała autoryzacja aplikacji na Androida jest zarządzana centralnie przez pakiet SDK za pomocą interfejsu AccountManager. Określasz zakres OAuth 2.0, którego potrzebuje Twoja aplikacja, a usługa zwraca token dostępu do użycia.

Zakres OAuth 2.0 jest określany za pomocą parametru authTokenType jako oauth2: plus zakres. Na przykład:

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

Określa uprawnienia do odczytu i zapisu interfejsu Google Tasks API. Jeśli potrzebujesz kilku zakresów OAuth 2.0, użyj listy rozdzielonej spacjami.

Niektóre interfejsy API mają specjalne parametry authTokenType, które też działają. Na przykład „Zarządzaj zadaniami” to alias dla authtokenType przykładu pokazanego powyżej.

Musisz też podać klucz interfejsu API z Konsoli interfejsów API Google. W przeciwnym razie token, który otrzymasz od AccountManager, zapewni Ci tylko anonimowy limit, który jest zwykle bardzo niski. Z kolei podanie klucza interfejsu API zapewnia wyższy bezpłatny limit, a opcjonalnie możesz też skonfigurować płatności za użycie powyżej tego limitu.

Przykładowy fragment kodu z 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;
  }
}