Использование OAuth 2.0 с клиентской библиотекой Google API для Java

Обзор

Цель: в этом документе объясняется, как использовать служебный класс GoogleCredential для авторизации OAuth 2.0 в службах Google. Информацию о предоставляемых нами общих функциях OAuth 2.0 см. в разделе OAuth 2.0 и Клиентской библиотеке Google OAuth для Java .

Краткое описание: Чтобы получить доступ к защищенным данным, хранящимся в сервисах Google, используйте для авторизации OAuth 2.0 . API Google поддерживают потоки OAuth 2.0 для различных типов клиентских приложений. Во всех этих потоках клиентское приложение запрашивает токен доступа, который связан только с вашим клиентским приложением и владельцем защищенных данных, к которым осуществляется доступ. Токен доступа также связан с ограниченной областью, которая определяет тип данных, к которым имеет доступ ваше клиентское приложение (например, «Управление задачами»). Важная цель OAuth 2.0 — обеспечить безопасный и удобный доступ к защищенным данным, сводя при этом к минимуму потенциальное воздействие в случае кражи токена доступа.

Пакеты OAuth 2.0 в клиентской библиотеке Google API для Java созданы на основе клиентской библиотеки Google OAuth 2.0 общего назначения для Java .

Подробную информацию см. в документации Javadoc для следующих пакетов:

Консоль Google API

Прежде чем вы сможете получить доступ к API Google, вам необходимо настроить проект в консоли API Google для целей аутентификации и выставления счетов, независимо от того, является ли ваш клиент установленным приложением, мобильным приложением, веб-сервером или клиентом, работающим в браузере.

Инструкции по правильной настройке учетных данных см. в справке консоли API .

Полномочия

GoogleCredential

GoogleCredential — это потокобезопасный вспомогательный класс для OAuth 2.0 для доступа к защищенным ресурсам с помощью токена доступа. Например, если у вас уже есть токен доступа, вы можете сделать запрос следующим образом:

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

Идентификация Google App Engine

Эти альтернативные учетные данные основаны на Java API Google App Engine App Identity . В отличие от учетных данных, в которых клиентское приложение запрашивает доступ к данным конечного пользователя, API Identity приложения предоставляет доступ к собственным данным клиентского приложения.

Используйте AppIdentityCredential (из google-api-client-appengine ). Эти учетные данные намного проще, поскольку Google App Engine берет на себя все детали. Вы указываете только необходимую вам область действия OAuth 2.0.

Пример кода взят из 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();
}

Хранилище данных

Срок действия токена доступа обычно составляет 1 час, после чего вы получите сообщение об ошибке, если попытаетесь его использовать. GoogleCredential автоматически «обновляет» токен, что означает просто получение нового токена доступа. Это делается с помощью долгоживущего токена обновления, который обычно получается вместе с токеном доступа, если вы используете параметр access_type=offline во время потока кода авторизации (см. GoogleAuthorizationCodeFlow.Builder.setAccessType(String) ).

Большинству приложений потребуется сохранить токен доступа и/или токен обновления учетных данных. Чтобы сохранить токены доступа и/или обновления учетных данных, вы можете предоставить собственную реализацию DataStoreFactory с помощью StoredCredential ; или вы можете использовать одну из следующих реализаций, предоставляемых библиотекой:

  • AppEngineDataStoreFactory : сохраняет учетные данные с помощью API хранилища данных Google App Engine.
  • MemoryDataStoreFactory : «сохраняет» учетные данные в памяти, которая полезна только в качестве краткосрочного хранилища на протяжении всего времени существования процесса.
  • FileDataStoreFactory : сохраняет учетные данные в файле.

Для пользователей AppEngine: AppEngineCredentialStore устарел и скоро будет удален. Мы рекомендуем использовать AppEngineDataStoreFactory со StoredCredential . Если у вас есть учетные данные, хранящиеся по-старому, вы можете использовать добавленные вспомогательные методыmigrTo (AppEngineDataStoreFactory) илиmigrateTo(DataStore) для выполнения миграции.

Вы можете использовать DataStoreCredentialRefreshListener и установить его для учетных данных с помощью GoogleCredential.Builder.addRefreshListener(CredentialRefreshListener) .

Поток кода авторизации

Используйте поток кода авторизации, чтобы позволить конечному пользователю предоставить вашему приложению доступ к своим защищенным данным в API Google. Протокол для этого потока указан в Предоставлении кода авторизации .

Этот поток реализуется с помощью GoogleAuthorizationCodeFlow . Шаги:

  • Конечный пользователь входит в ваше приложение. Вам нужно будет связать этого пользователя с идентификатором пользователя, уникальным для вашего приложения.
  • Вызовите AuthorizationCodeFlow.loadCredential(String) ) на основе идентификатора пользователя, чтобы проверить, известны ли уже учетные данные конечного пользователя. Если да, то мы закончили.
  • Если нет, вызовите AuthorizationCodeFlow.newAuthorizationUrl() и направьте браузер конечного пользователя на страницу авторизации, чтобы предоставить вашему приложению доступ к его защищенным данным.
  • Затем сервер авторизации Google перенаправит браузер обратно на URL-адрес перенаправления, указанный вашим приложением, вместе с параметром запроса code . Используйте параметр code , чтобы запросить токен доступа с помощью AuthorizationCodeFlow.newTokenRequest(String) ).
  • Используйте AuthorizationCodeFlow.createAndStoreCredential(TokenResponse, String) ) для хранения и получения учетных данных для доступа к защищенным ресурсам.

В качестве альтернативы, если вы не используете GoogleAuthorizationCodeFlow , вы можете использовать классы более низкого уровня:

  • Используйте DataStore.get(String) для загрузки учетных данных из хранилища на основе идентификатора пользователя.
  • Используйте GoogleAuthorizationCodeRequestUrl , чтобы направить браузер на страницу авторизации.
  • Используйте AuthorizationCodeResponseUrl для обработки ответа на авторизацию и анализа кода авторизации.
  • Используйте GoogleAuthorizationCodeTokenRequest , чтобы запросить токен доступа и, возможно, токен обновления.
  • Создайте новый GoogleCredential и сохраните его с помощью DataStore.set(String, V) .
  • Получите доступ к защищенным ресурсам с помощью GoogleCredential . Токены доступа с истекшим сроком действия будут автоматически обновлены с использованием токена обновления (если применимо). Обязательно используйте DataStoreCredentialRefreshListener и установите его для учетных данных с помощью GoogleCredential.Builder.addRefreshListener(CredentialRefreshListener) .

Когда вы настраиваете свой проект в консоли Google API , вы выбираете различные учетные данные в зависимости от используемого потока. Дополнительные сведения см. в разделе Настройка сценариев OAuth 2.0 и OAuth 2.0 . Фрагменты кода для каждого из потоков приведены ниже.

Приложения веб-сервера

Протокол этого потока описан в разделе Использование OAuth 2.0 для приложений веб-сервера .

Эта библиотека предоставляет вспомогательные классы сервлетов, которые значительно упрощают поток кода авторизации для основных случаев использования. Вы просто предоставляете конкретные подклассы AbstractAuthorizationCodeServlet и AbstractAuthorizationCodeCallbackServlet (из google-oauth-client-servlet ) и добавляете их в свой файл web.xml. Обратите внимание, что вам все равно необходимо позаботиться о входе пользователя в ваше веб-приложение и извлечь идентификатор пользователя.

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

Приложения Google App Engine

Поток кода авторизации в App Engine почти идентичен потоку кода авторизации сервлета, за исключением того, что мы можем использовать Java API пользователей Google App Engine. Чтобы включить Users Java API, пользователю необходимо войти в систему; информацию о перенаправлении пользователей на страницу входа, если они еще не вошли в систему, см. в разделе Безопасность и аутентификация (в web.xml).

Основное отличие от случая с сервлетом заключается в том, что вы предоставляете конкретные подклассы AbstractAppEngineAuthorizationCodeServlet и AbstractAppEngineAuthorizationCodeCallbackServlet (из google-oauth-client-appengine . Они расширяют абстрактные классы сервлетов и реализуют для вас метод getUserId с использованием Users Java API. AppEngineDataStoreFactory (из google) -http-client-appengine ) — хороший вариант для сохранения учетных данных с помощью API хранилища данных Google App Engine.

Пример взят (слегка изменен) из 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();
  }
}

Дополнительный пример см. в разделе Storage-serviceaccount-appengine-sample .

Сервисные аккаунты

GoogleCredential также поддерживает учетные записи служб . В отличие от учетных данных, с помощью которых клиентское приложение запрашивает доступ к данным конечного пользователя, учетные записи служб предоставляют доступ к собственным данным клиентского приложения. Ваше клиентское приложение подписывает запрос токена доступа с помощью закрытого ключа, загруженного из консоли Google API .

Пример кода взят из 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();
...

Дополнительный пример см. в разделе Storage-serviceaccount-cmdline-sample .

Олицетворение

Вы также можете использовать поток учетной записи службы, чтобы олицетворять пользователя в принадлежащем вам домене. Это очень похоже на приведенный выше поток учетной записи службы, но вы дополнительно вызываете GoogleCredential.Builder.setServiceAccountUser(String) .

Установленные приложения

Это поток кода авторизации командной строки, описанный в разделе Использование OAuth 2.0 для установленных приложений .

Пример фрагмента из 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");
}

Клиентские приложения

Чтобы использовать клиентский поток на основе браузера, описанный в разделе «Использование OAuth 2.0 для клиентских приложений» , обычно следует выполнить следующие действия:

  1. Перенаправьте конечного пользователя в браузере на страницу авторизации с помощью GoogleBrowserClientRequestUrl , чтобы предоставить приложению вашего браузера доступ к защищенным данным конечного пользователя.
  2. Используйте клиентскую библиотеку Google API для JavaScript для обработки токена доступа, найденного во фрагменте URL-адреса по URI перенаправления, зарегистрированному в консоли Google API .

Пример использования веб-приложения:

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:

Если вы разрабатываете приложение для Android и API Google, который вы хотите использовать, включен в библиотеку сервисов Google Play , используйте эту библиотеку для обеспечения наилучшей производительности и удобства работы. Если API Google, который вы хотите использовать с Android, не является частью библиотеки сервисов Google Play, вы можете использовать клиентскую библиотеку Google API для Java, которая поддерживает Android 4.0 (Ice Cream Sandwich) (или более позднюю версию) и которая описана здесь. . Поддержка Android в клиентской библиотеке Google API для Java — @Beta .

Фон:

Начиная с Eclair (SDK 2.1), управление учетными записями пользователей на устройстве Android осуществляется с помощью диспетчера учетных записей. Вся авторизация приложений Android централизованно управляется SDK с помощью AccountManager . Вы указываете область действия OAuth 2.0, необходимую вашему приложению, и оно возвращает токен доступа для использования.

Область OAuth 2.0 указывается с помощью параметра authTokenType как oauth2: плюс область. Например:

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

Это определяет доступ для чтения и записи к API задач Google. Если вам нужно несколько областей OAuth 2.0, используйте список, разделенный пробелами.

Некоторые API имеют специальные параметры authTokenType , которые также работают. Например, «Управление задачами» — это псевдоним для примера authtokenType , показанного выше.

Также необходимо указать ключ API из консоли Google API . В противном случае токен, который дает вам AccountManager, предоставляет вам только анонимную квоту, которая обычно очень мала. Напротив, указав ключ API, вы получаете более высокую бесплатную квоту и можете дополнительно настроить оплату за использование сверх этой суммы.

Пример фрагмента кода, взятого из 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;
  }
}