Java 用 Google API クライアント ライブラリで OAuth 2.0 を使用する

概要

目的: このドキュメントでは、GoogleCredential ユーティリティ クラスを使用して、Google サービスで OAuth 2.0 認証を行う方法について説明します。Google が提供する汎用の OAuth 2.0 関数については、OAuth 2.0 と Java 用 Google OAuth クライアント ライブラリをご覧ください。

概要: Google サービスに保存されている保護されたデータにアクセスするには、認証に OAuth 2.0 を使用します。Google API は、さまざまな種類のクライアント アプリケーション向けに OAuth 2.0 フローをサポートしています。これらすべてのフローで、クライアント アプリケーションは、クライアント アプリケーションとアクセス対象の保護データのオーナーのみに関連付けられたアクセス トークンをリクエストします。アクセス トークンは、クライアント アプリケーションがアクセスできるデータの種類を定義する限られたスコープにも関連付けられます(「タスクの管理」など)。OAuth 2.0 の重要な目標は、アクセス トークンが盗まれた場合の潜在的な影響を最小限に抑えながら、保護されたデータへの安全で便利なアクセスを提供することです。

Java 用 Google API クライアント ライブラリの OAuth 2.0 パッケージは、汎用の Java 用 Google OAuth 2.0 クライアント ライブラリに基づいて構築されています。

詳細については、次のパッケージの Javadoc ドキュメントをご覧ください。

Google API Console

Google API にアクセスするには、クライアントがインストール済みのアプリケーション、モバイルアプリ、ウェブサーバー、ブラウザで動作するクライアントのいずれであっても、認証と課金のために Google API Console でプロジェクトをセットアップしておく必要があります。

認証情報を適切に設定する手順については、API Console ヘルプをご覧ください。

クルデンシャル

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 の ID

この代替認証情報は、Google App Engine App Identity Java API に基づいています。クライアント アプリケーションがエンドユーザーのデータへのアクセスをリクエストする認証情報とは異なり、App Identity API はクライアント アプリケーション自身のデータへのアクセスを提供します。

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) をご覧ください)。

ほとんどのアプリケーションでは、認証情報のアクセス トークンや更新トークンを保持する必要があります。認証情報のアクセス トークンや更新トークンを保持するには、StoredCredential を使用して DataStoreFactory の独自の実装を指定するか、ライブラリで提供される次のいずれかの実装を使用します。

  • AppEngineDataStoreFactory: Google App Engine Data Store API を使用して認証情報を保持します。
  • MemoryDataStoreFactory: 認証情報をメモリに「保持」します。これは、プロセスの存続期間中の短期的なストレージとしてのみ有用です。
  • FileDataStoreFactory: 認証情報をファイル内に保持します。

App Engine ユーザー: AppEngineCredentialStore は非推奨になり、まもなく削除されます。AppEngineDataStoreFactoryStoredCredential を使用することをおすすめします。従来の方式で認証情報が保存されている場合は、追加されたヘルパー メソッド migrateTo(AppEngineDataStoreFactory) または migrateTo(DataStore) を使用して移行できます。

DataStoreCredentialRefreshListener を使用し、GoogleCredential.Builder.addRefreshListener(CredentialRefreshListener) を使用して認証情報を設定できます。

認可コードフロー

認可コードフローを使用して、Google API 上の保護されたデータへのアクセス権をエンドユーザーがアプリケーションに付与できるようにします。このフローのプロトコルは、認可コード付与で規定されています。

このフローは GoogleAuthorizationCodeFlow を使用して実装されています。ステップは次のとおりです。

  • エンドユーザーがアプリケーションにログインします。そのユーザーをアプリケーションに固有のユーザー ID に関連付ける必要があります。
  • ユーザー ID に基づいて AuthorizationCodeFlow.loadCredential(String))を呼び出し、エンドユーザーの認証情報がすでにわかっているかどうかを確認します。動作すれば完了です。
  • 承認されていない場合は、AuthorizationCodeFlow.newAuthorizationUrl() を呼び出し、エンドユーザーのブラウザから認証ページを開き、保護されたデータへのアクセス権をアプリケーションに付与してください。
  • Google 認可サーバーは、アプリケーションで指定されたリダイレクト URL に、code クエリ パラメータとともにブラウザをリダイレクトします。AuthorizationCodeFlow.newTokenRequest(String) でアクセス トークンをリクエストするには、code パラメータを使用します。
  • AuthorizationCodeFlow.createAndStoreCredential(TokenResponse, String) を使用して、保護されたリソースにアクセスするための認証情報を保存し、取得します。

GoogleAuthorizationCodeFlow を使用しない場合は、次のように下位レベルのクラスを使用することもできます。

Google API Console でプロジェクトを設定する際は、使用しているフローに応じてさまざまな認証情報から選択します。詳細については、OAuth 2.0 の設定OAuth 2.0 のシナリオをご覧ください。各フローのコード スニペットを以下に示します。

ウェブサーバーアプリケーション

このフローのプロトコルについては、ウェブサーバー アプリケーションに OAuth 2.0 を使用するをご覧ください。

このライブラリには、基本的なユースケースの認可コードフローを大幅に簡素化するためのサーブレット ヘルパークラスが用意されています。AbstractAuthorizationCodeServletAbstractAuthorizationCodeCallbackServletgoogle-oauth-client-servlet)の具体的なサブクラスを用意して、web.xml ファイルに追加するだけです。この場合も、ウェブ アプリケーションのユーザー ログインを考慮し、ユーザー ID を抽出する必要があります。

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 の認可コードフローは、Google App Engine の Users Java API を利用できる点を除き、サーブレット認可コードフローとほぼ同じです。Users Java API を有効にするには、ユーザーがログインする必要があります。ユーザーがまだログインしていない場合にユーザーをログインページにリダイレクトする方法については、セキュリティと認証(web.xml 内)をご覧ください。

サーブレットの場合との主な違いは、AbstractAppEngineAuthorizationCodeServletAbstractAppEngineAuthorizationCodeCallbackServletの具体的なサブクラス(google-oauth-client-appengine から取得)を指定する点です。抽象サーブレットクラスを拡張し、Users Java API を使用して getUserId メソッドを実装します。Google App Engine Data Store API を使用して認証情報を保持する場合は、AppEngineDataStoreFactorygoogle-http-client-appengine から)が適しています。

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. JavaScript の Google API クライアント ライブラリを使用して、Google API Console で登録されたリダイレクト URI の URL フラグメントに含まれるアクセス トークンを処理します。

ウェブ アプリケーションの使用例:

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

Android で使用するライブラリ:

Android 向けの開発を行っており、使用する Google API が Google Play 開発者サービス ライブラリに含まれている場合、最適なパフォーマンスとエクスペリエンスが得られるようにライブラリを使用してください。Android で使用する Google API が Google Play 開発者サービス ライブラリに含まれていない場合は、Java 用 Google API クライアント ライブラリを使用できます。このライブラリは Android 4.0(Ice Cream Sandwich)(またはそれ以上)をサポートしています。これについてはこちらで説明しています。Java 用 Google API クライアント ライブラリでの Android のサポートは @Beta です。

バックグラウンド:

Eclair(SDK 2.1)以降では、アカウント マネージャーを使用して Android デバイスでユーザー アカウントを管理できます。Android アプリの承認はすべて、AccountManager を使用して SDK で一元的に管理されます。アプリケーションに必要な OAuth 2.0 スコープを指定すると、使用するアクセス トークンが返されます。

OAuth 2.0 スコープは、authTokenType パラメータ(oauth2: とスコープ)で指定されます。例:

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

Google Tasks API への読み取り/書き込みアクセス権を指定します。複数の OAuth 2.0 スコープが必要な場合は、スペース区切りリストを使用します。

一部の API には特別な authTokenType パラメータもあります。たとえば、「Manage your tasks」は上記の authtokenType サンプルのエイリアスです。

Google API Console でも 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;
  }
}