Использование 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 для следующих пакетов:

Консоль API Google

Прежде чем вы сможете получить доступ к 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-интерфейс App 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 . Если у вас есть старые учетные данные, вы можете использовать добавленные вспомогательные методы migrateTo(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 Console , вы выбираете различные учетные данные в зависимости от используемого потока. Дополнительные сведения см. в разделе Настройка 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 почти идентичен потоку кода авторизации сервлета, за исключением того, что мы можем использовать Users 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 Console .

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

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

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 , используйте эту библиотеку для достижения наилучшей производительности и удобства. Если Google API, который вы хотите использовать с Android, не является частью библиотеки Google Play Services, вы можете использовать клиентскую библиотеку 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 Tasks. Если вам нужно несколько областей действия OAuth 2.0, используйте список, разделенный пробелами.

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

Также необходимо указать ключ API из Google API Console . В противном случае токен, который дает вам 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;
  }
}