GeekFactory

int128.hatenablog.com

WebアプリからGoogle Tasks APIにアクセスする

Google Tasks APIはタスクにアクセスするためのAPIです。GoogleカレンダーにTo Doリストが付いていますよね、あれです。

WebアプリからGoogle Tasks APIにアクセスするにはクライアントサイド(JavaScript)とサーバサイドの2つの方法がありますが、今回はサーバサイドを説明します。

必要なもの。

事前準備として、Google API ConsoleのTasks APIにアクセスしてClient IDとClient Secretを取得します。また、リダイレクトURLのホワイトリストを指定しておきます。リダイレクトURLはlocalhostも入れておきましょう。

おおまかな流れを以下に示します。

  1. アプリのトップページが表示される。
  2. ユーザがログインリンクをクリックすると、Googleの認可画面が表示される。
  3. ユーザがリソースへのアクセスを許可すると、アプリにリダイレクトされる。
  4. アプリはGoogleからトークンを取得し、セッションに保存する。
  5. アプリはTasks APIからデータを取得し、ページを返す。

Step1:トップページの表示

トップページは静的なHTMLで構いません。ここでは https://www.example.com/ とします。

トップページにはログインリンクを配置します。正確には、認可要求URLへのリンクです。リンク先は https://accounts.google.com/o/oauth2/auth に以下のパラメータを付けたものになります。詳しくはGoogle APIsの説明に書いてあります。

パラメータ名
redirect_uri リダイレクト先のURLを指定します。
client_id Client IDを指定します。
scope アクセス対象のリソースを指定します。ここではユーザのタスクを読み書きしたいので、 https://www.googleapis.com/auth/tasks を指定します。
response_type codeを指定します。
<a class="session-login"
href="https://accounts.google.com/o/oauth2/auth?redirect_uri=https://www.example.com/&response_type=code&scope=https://www.googleapis.com/auth/tasks&client_id=xxxCLIENTxIDxxx.apps.googleusercontent.com">
Googleアカウントでログイン
</a>

サーバサイドでURLを生成することも可能です。

String redirectURL = ...; // リダイレクト先のURLを組み立てる
String authorizationURL = new GoogleAuthorizationRequestUrl(
  Constants.clientCredential.getClientId(),  // Client ID
  redirectURL,
  "https://www.googleapis.com/auth/tasks")
  .build();
// TODO: authorizationURLをJSPに投げる

クライアントサイドだとこんな感じ。

$('a.session-login').attr('href', 'https://accounts.google.com/o/oauth2/auth'
  + '?redirect_uri=' + location.protocol + '//' + location.host + location.pathname
  + '&response_type=code'
  + '&scope=https://www.googleapis.com/auth/tasks'
  + '&client_id=xxxCLIENTxIDxxx.apps.googleusercontent.com');

Step2:認可確認

ユーザに認可を求める画面が表示されます。

Step3:リダイレクト

ユーザがアクセスを許可した場合は認可コードが発行され、Step1で指定したURLにリダイレクトされます。

ユーザがアクセスを拒否した場合はerrorパラメータ付きでリダイレクトされます。

Step4:トークンの取得

リダイレクト先のサーブレットでは、認可コードからアクセストークンを取得します。アクセストークンの取得はクライアントライブラリの GoogleAuthorizationCodeGrant がやってくれます。具体的にはこんな感じ。

// https://www.example.com/callback のサーブレット
// TODO: errorパラメータが付いている場合はトップページに戻す
    String authorizationCode = asString("code");
    String redirectURI = ...;  // Step1で指定したリダイレクト先
    GoogleAuthorizationCodeGrant grant = new GoogleAuthorizationCodeGrant(
        NetHttpTransportLocator.get(),  // NetHttpTransportのシングルトン
        JacksonFactoryLocator.get(),  // JacksonFactoryのシングルトン
        Constants.clientCredential.getClientId(),  // Client ID
        Constants.clientCredential.getClientSecret(),  // Client Secret
        authorizationCode,      
        redirectURI);           
    AccessTokenResponse tokenResponse = execute(grant);
    // Serializableに入れてセッションに保存
    CachedToken token = new CachedToken(tokenResponse.accessToken, tokenResponse.refreshToken);

なお、サーブレットが処理している間はWebブラウザが空白表示になります。リダイレクト先をHTMLにしておいてAjaxサーブレットを叩くようにすると、体感的な待ち時間の短縮が期待できます。

  var authorizationCodeMatch = location.search.match(/\?code=(.*)/);
  if (authorizationCodeMatch) {
    // セッションを開始する
    $.post('/callback', {code: authorizationCodeMatch[1]}, function () {
      location.replace(location.pathname);
    });
  }
  else if (location.search == '?error=access_denied') {
    location.replace(location.pathname);
  }
  else if (...) {
    // セッションがすでに存在する場合

Step5:リソースへのアクセス

Tasks APIにアクセスするにはクライアントライブラリのTasksクラスを使います。同名のクラスが2つあって非常に紛らわしいため、スコープが重複しないように使った方がよいでしょう。

  • com.google.api.services.tasks.Tasks はサービスを利用するためのクラス
  • com.google.api.services.tasks.model.Tasks はモデルを表現するクラス

事前準備を前処理として切り出します。抽象クラスのController#setUp()に入れておくなど。

public abstract class ControllerBase extends Controller {
  protected Navigation setUp() {
    // サービスを利用する準備
    CachedToken token = ...; // セッションから取り出す
    HttpTransport httpTransport = NetHttpTransportLocator.get();
    JacksonFactory jsonFactory = JacksonFactoryLocator.get();
    GoogleAccessProtectedResource resource = new GoogleAccessProtectedResource(
        token.getAccessToken(),
        httpTransport,
        jsonFactory,
        Constants.clientCredential.getClientId(),
        Constants.clientCredential.getClientSecret(),
        token.getRefreshToken());
    this.tasksService = new Tasks(httpTransport, resource, jsonFactory);

モデルを扱う部分では、サービスのTasksに依存しないようにします。上記を実装したクラスを継承するなど。

public class ListController extends ControllerBase {
  public Navigation run() throws Exception {
    // Tasks APIにアクセスする
    String tasklistID = asString("tasklistID");
    Tasks tasks = this.tasksService.tasks.list(tasklistID).execute();

クライアントライブラリの使い方は Write your First App: Prerequisites - Google Apps Platform — Google Developers で詳しく説明されています。タスク管理のモデルは奥が深いですね。