Google Calendar APIの認証はOAuth 2.0を利用しており、Webアプリケーション、クライアント・アプリケーションなどのシナリオをサポートしています。
(参照: Using OAuth 2.0 to Access Google APIs)

Google Appsを利用していて、企業向けのサービスや社内システムとGoogle Calendarを連携したい場合、システム単位でユーザーにアクセス許可を求めずに、ドメイン単位でGoogle Appsへのアクセスを設定したいところです。
このようなサーバー間およびバッチ(デーモン)アプリケーションのシナリオでは、JWT(Json Web Token)でサーバー用のアクセストークンを取得する方法が利用できます(2 Legged OAuthとも言う)。

サーバー間用のアクセストークンは、Google Appsのドメイン全体の委任を有効にしたサービス アカウントを作成し、サービス アカウントの秘密鍵を使って生成した署名を含むJWTをhttps://www.googleapis.com/oauth2/v4/tokenにPOSTすることで取得できます。
(参照: Using OAuth 2.0 for Server to Server Applications)

この記事は、サービス アカウントでGoogle Calendar APIへアクセスするシナリオでのJava API クライアント ライブラリの使い方について説明していきます。
簡単に流れは説明しましたが、クライアント ライブラリを利用するので、JWTの作成およびアクセストークンの取得についてはライブラリにおまかせです。
(参照: Calendar API Client Library for Java)

説明用のコードは、以下のJava API クライアントのサンプルのcalendar-cmdline-sampleを使います。
google/google-api-java-client-samples
このサンプルは、ユーザーに認可を求めるクライアント・アプリケーションです。
まずは元の動作を確認し、その後、サービスアカウント認証へ実装を変更していく流れで説明していきます。

前提条件

クライアントライブラリは、Javaのパッケージ管理システムMavenを利用します。

Mac OSXでHomebrewを利用している場合は、以下のコマンドで安定版の最新をインストールできます。

$ brew install maven

プロジェクトの準備

サンプルコードをクローンし、mvnコマンドでライブラリをダウンロードします。

$ git clone git@github.com:google/google-api-java-client-samples.git
$ cd google-api-java-client-samples/calendar-cmdline-sample
$ mvn install

サンプルコードの動作確認

calendar-cmdline-sampleは、インストールアプリケーションのシナリオの例で、ユーザーはGoogleにログインしてアプリケーションにAPIアクセスを許可する必要があります。

OAuth クライアント IDを作成して、オリジナルのサンプルの動きを確認して見ます。

OAuth クライアント IDの作成

Google Developer Consoleにログインして、OAuth クライアント ID を生成します。
calendar-cmdline-sample-create-client-id-01

[ウェブ アプリケーション]を選択して、名前を適当に入力して、承認済みのリダイレクト URI http://localhost:9000/Callbackを指定します。
calendar-cmdline-sample-create-client-id-02

[作成]クリックすると[クライアント ID]とシークレットが表示されます。calendar-cmdline-sample-create-client-id-03

client_secrets.jsonに表示されたクライアント IDとシークレットをコピーします。

{
  "installed": {
    "client_id": "Enter Client ID",
    "client_secret": "Enter Client Secret"
  }
}

コールバックポートを固定する

そのままのコードでは、コールバックのポートは使用していないポートをランダムに選択されてしまいます(Class LocalServerReceiver)。

CalendarSample.java を以下のように修正して、リダイレクト URIに設定できるようにポートを9000に固定します。

    return new AuthorizationCodeInstalledApp(flow, new LocalServerReceiver.Builder().setPort(9000).build()).authorize("user");

動作確認

mvnでアプリケーションをコンパイルして実行します。

$ mvn compile
$ mvn exec:java -Dec.mainClass="com.google.api.services.samples.calendar.cmdline.CalendarSample"

ブラウザが立ち上がり、アプリケーションからGoogleカレンダーへのアクセス許可が求められます。
request-access-to-google-calendar-api
アクセスを許可すると、アプリケーションの実行結果がターミナルに表示されます。

============== Show Calendars ==============

-----------------------------------------------
ID: foo@example.net
Summary: Foo

-----------------------------------------------
ID: ja.japanese#holiday@group.v.calendar.google.com
Summary: 日本の祝日
Description: 日本の祝日と行事

....

-----------------------------------------------
Start Time: {"dateTime":"2016-06-06T09:33:44.000Z"}
End Time: {"dateTime":"2016-06-06T10:33:44.000Z"}

============== Delete Calendars Using Batch ==============

Delete is successful!
Delete is successful!

============== Delete Calendar ==============

再度実行すると、~/.store/calendar_sample/StoredCredential に保存されたアクセストークンが利用されます。

サービス アカウント認証の実装

ここから、動作を確認したコードを修正して、サービス アカウント認証を実装していきます。

サービス アカウントの作成

まずは、サービスア カウントを作成して、JWTに署名をつけるための秘密キー(p12形式)を取得します。

Google Developer ConsoleのAPI Managerを開き[認証情報を作成] > [サービス アカウント キー]を選択します。
google-calendar-create-service-account-01

[サービスアカウント]は「新しいサービスアカウント」を選択。適当な[サービス アカウント名]を入力し、[キーのタイプ]は「p12」を選択しします。
google-calendar-create-service-account-02
[作成]をクリックすると、P12形式の秘密鍵がダウンロードされ以下の確認が表示されます。
google-calendar-create-service-account-03
[完了]をクリックして閉じます。

サービス アカウントのクライアント IDを取得

作成したサービス アカウントでは、まだGoogle Appsへのアクセスができません。
サービス アカウントGoogle Appsのドメイン全体の委任を有効にします。

以下の画面で、[サービス アカウントの管理]をクリックするか、サイドメニューから[IAM と 管理] > [サービス アカウント]を開きます。
google-calendar-create-service-account-04

作成したサービス アカウントを編集します。
google-calendar-create-service-account-05

[Google Appsのドメイン全体の委任を有効にする]にチェックを入れて[保存]します。
google-calendar-create-service-account-06

オプションに[DwD クライアント IDを表示]と表示されました。
google-calendar-create-service-account-07
[クライアント IDを表示]をクリックすると以下の画面が表示されてサービスアカウントのクライアント IDを確認できます。

google-calendar-create-service-account-08

Google Appsへの登録

サービス アカウントのクライアント IDをGoogle Appsに登録すると、JWTで取得したアクセストークンを使ってGoogle AppsのCalendar APIにアクセスできるようになります。

Google Appsの管理コンソールを開き、[セキュリティ] > [詳細設定] > [API クライアント アクセスを管理する]を開きます。
google-calendar-set-service-account
[クライアント名]にサービス アカウントのクライアント IDを入力して、[1つ以上の API の範囲]に「 https://www.googleapis.com/auth/calendar」と入力して[承認]ボタンをクリックします。

以上で、サービス アカウント キーを利用してGoogle Appsへアクセスできるようになりました。

コードの修正

Credentialオブジェクトを生成するauthorizaメソッドを以下のように修正します。

...
import java.util.Arrays;
import java.io.File;
import java.io.InputStream;
import java.io.FileInputStream;
import java.security.PrivateKey;
import com.google.api.client.util.SecurityUtils;
import com.google.api.client.googleapis.auth.oauth2.GoogleCredential;

public class CalendarSample {
  ...
  /** Authorizes the installed application to access user's protected data. */
  private static Credential authorize() throws Exception {
    // サービス アカウントのP12形式の秘密鍵をPrivateKeyオブジェクトとして読み込む
    InputStream in = new FileInputStream(new File("Google Calendar Sample-xxxxxxxxxx.p12"));
    PrivateKey privateKey = SecurityUtils.loadPrivateKeyFromKeyStore(SecurityUtils.getPkcs12KeyStore(), in, "notasecret", "privatekey", "notasecret");
    // 読み込んだ秘密キーを署名したJWTを生成するCredentialを生成
    return new GoogleCredential.Builder()
      .setTransport(httpTransport)
      .setJsonFactory(JSON_FACTORY)
      .setServiceAccountId("calendar-cmdline-sample@xxxxxx-yyyy-1234567.iam.gserviceaccount.com")
      .setServiceAccountScopes(Arrays.asList("https://www.googleapis.com/auth/calendar"))
      .setServiceAccountPrivateKey(privateKey)
      .setServiceAccountUser("suzuki@example.com")
    .build();
  }
  ...

GoogleCredential.Builder()のメソッドについて補足します。

  • setServiceAccountId: メールアドレス形式のサービス アカウント ID
  • setServiceAccountScopes: 利用するAPIのスコープの配列
  • setServiceAccountPrivateKey: サービス アカウントの秘密鍵
  • setServiceAccountUser: リクエストを実行するユーザー(主体者)のアカウント

実装の変更は、これだけでJWTの作成やアクセストークンの取得は設定情報を使ってライブラリが処理をしてくれます。

参考: Java Code Examples for com.google.api.client.util.SecurityUtils

動作確認

コンパイルして動作を確認します。

実行する前に、前回実行時に保存されたアクセストークンを削除してから実行します。

$ mvn clean
$ mvn compile
$ rm ~/.store/calendar_sample/StoredCredential
$ mvn exec:java -Dec.mainClass="com.google.api.services.samples.calendar.cmdline.CalendarSample"

実行結果は同じですが、今回は、ブラウザでログインする必要なくGoogle Calendar APIへアクセスできることが確認できました。