Google OAuthを使ってGAEアプリからGoogle Calendarのデータを取得する(やっつけぎみ)

Google App Engineで作ったアプリからGoogle Calendarのデータを使いたかったりしたので、調べてみた。
最初AutuSub認証でやろうと思ってたんだけどなんか、違うと思って、OAuthでやることにした。AuthSubとOAuthの役割って同じなんだろうか。OAuthはオープンな仕様で、AuthSubはそうじゃないってだけ?

ちなみに、OAuthはコンシューマとサービスプロバイダとユーザという3つのアクターが登場する。今回、自分の立場はコンシューマになる。サービスプロバイダがGoogleで、ユーザはコンシューマが作ったサービスとサービスプロバイダの利用者だ。
以下のサイトがOAuthについてわかりやすく説明されてる。
第1回 OAuthとは?―OAuthの概念とOAuthでできること:ゼロから学ぶOAuth|gihyo.jp … 技術評論社

また、OAuthではサービスプロバイダ(今回はGoogle)にコンシューマ登録をしてConsumer KeyとConsumer Secretの2つのキーを取得するようだけど、以下のサイトにanonymous OAuthなるもので、コンシューマ登録をしなくても認証が出来るようになるってことが書かれていたのでそれを試してみる。
Anonymous OAuth Test

まず必要なライブラリを以下のサイトからダウンロード

GitHub - google/gdata-java-client: Automatically exported from code.google.com/p/gdata-java-client
現時点(2009/11/13)で最新の「gdata-src.java-1.39.1.zip」をダウンロードしてきて、必要なライブラリを「WEB-INF/lib」の中に入れ、ビルドパスに通す。

  • gdata/java/lib
    • gdata-core-1.0.jar
    • gdata-client-meta-1.0.jar
    • gdata-client-1.0.jar
    • gdata-calendar-meta-2.0.jar
    • gdata-calendar-2.0.jar
  • gdata/java/deps
    • google-collect-1.0-rc1.jar
    • jsr305.jar

サンプルアプリ

今回のサンプルは、OAuth認証が完了したあとGoogle Calendarにアクセスしカレンダーの一覧をとって来るという簡単なもの。ちなみに、Slim3(http://slim3.org)を使って実装しています。

OAuthの流れ

OAuthを使って認証を行うときの流れは以下のサイトの図がわかりやすい。
http://code.google.com/intl/ja/apis/accounts/docs/OAuthForInstalledApps.html
また以下のサイトではOAuthのステップを一つずつ試せるので、何度か動かしているとOAuthの流れをイメージしやすいかも。
http://googlecodesamples.com/oauth_playground/
大雑把にいうとだいたい以下のような感じ

  • コンシューマのサービスにアクセス
  • コンシューマのサービスからGoogleのサービスにアクセスする場合、OAuthの準備をして、Googleの認証ページにリダイレクト
  • Googleにログインしていなかったらログイン
  • コンシューマのアプリからサービス(Google Calendarとか)にアクセスを許すか決める
  • コンシューマのアプリに戻ってくる
  • コンシューマはGoogleのサービスにアクセスしてデータを取得したりできるようになる。

サンプルアプリのコード

色々参考にさせていただきました。
Opus24 OAuthやってみる ?その1? Google Data APIs で OAuth Consumer を作ってみる
Google Data APIのOAUTHに挑戦 - 気楽なソフト工房
http://code.google.com/intl/ja/apis/gdata/docs/auth/oauth.html

package sample.controller.login;

import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URL;
import java.net.URLEncoder;
import java.util.logging.Logger;

import org.slim3.controller.Controller;
import org.slim3.controller.Navigation;
import org.slim3.util.StringUtil;

import com.google.gdata.client.authn.oauth.GoogleOAuthHelper;
import com.google.gdata.client.authn.oauth.GoogleOAuthParameters;
import com.google.gdata.client.authn.oauth.OAuthException;
import com.google.gdata.client.authn.oauth.OAuthHmacSha1Signer;
import com.google.gdata.client.authn.oauth.OAuthSigner;
import com.google.gdata.client.calendar.CalendarService;
import com.google.gdata.data.calendar.CalendarFeed;
import com.google.gdata.util.ServiceException;

// このIndexControllerはhttp://localhost:8080/loginでアクセスできるように設定
public class IndexController extends Controller {

    private static final Logger logger =
        Logger.getLogger(IndexController.class.getName());
    
    @Override
    protected Navigation run() {
	// 
        String oauthTokenSecret = asString("oauth_token_secret");
        if (StringUtil.isEmpty(oauthTokenSecret)) {
            // 初回アクセス
            return request();
        } else {
            // Googleで認証された後、このアプリに戻ってきたとき
            return getAccessToken(oauthTokenSecret);
        }
    }

    private Navigation getAccessToken(String oauthTokenSecret) {
        GoogleOAuthParameters params = new GoogleOAuthParameters();
        params.setOAuthConsumerKey("anonymous"); // Anonymous OAuthを使う
        params.setOAuthConsumerSecret("anonymous"); // Anonymous OAuthを使う
        params.setOAuthToken(asString("oauth_token")); // 取得したOAuth token.
        params.setOAuthTokenSecret(oauthTokenSecret); // getOAuthTokenSecretで取得したOAuth token secret
        params.setOAuthSignatureMethod("HMAC-SHA1");
	// OAuth tokenとOAuth token secretを保存しておいて、次回のアクセスに使うのかな。。。
        
        OAuthSigner signer = new OAuthHmacSha1Signer();
        GoogleOAuthHelper helper = new GoogleOAuthHelper(signer);
        String token = null;
        try {
	    // access tokenを取得できる。けど、これはなにに使うの?
            token = helper.getAccessToken(params);
            logger.info("Access Token: " + token);
            
            // Google Calendarにアクセス
            CalendarService service = new CalendarService("Sample Web"); // この名前はなんだろう。。
                service.setOAuthCredentials(params, signer);

	    // ここでようやくカレンダーにアクセス
            URL feedUrl = new URL("http://www.google.com/calendar/feeds/default/owncalendars/full");
	    CalendarFeed calFeed = service.getFeed(feedUrl, CalendarFeed.class);
	    requestScope("entries", calFeed.getEntries());
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ServiceException e) {
            e.printStackTrace();
        } catch (OAuthException e) {
            e.printStackTrace();
        }
	// 取得したカレンダーの内容を表示するJSPにフォワード
        return forward("calendar_list.jsp");
    }

    private Navigation request() {
        logger.info("START authRequest.");
        GoogleOAuthParameters params = new GoogleOAuthParameters();
        params.setOAuthConsumerKey("anonymous"); // Anonymous OAuthを使う
        params.setOAuthConsumerSecret("anonymous"); // Anonymous OAuthを使う
        
        OAuthSigner signer = new OAuthHmacSha1Signer();
        GoogleOAuthHelper helper = new GoogleOAuthHelper(signer);
        
	// Goolge Calendarへアクセスを要求するためのスコープを設定
        params.setScope("http://www.google.com/calendar/feeds/");
        try {
	    // Step1,2 Unauthorized requests token
            helper.getUnauthorizedRequestToken(params);

	    // この時点でOAuthTokenとOAuthTokenSecretが取得される
            logger.info("OAuth Token: " + params.getOAuthToken());
            logger.info("OAuth Token Secret: " + params.getOAuthTokenSecret());
        
	    // Googleの認証サービスからこのアプリに戻ってくるためのURLを設定
            String secretToken = "?oauth_token_secret=" + 
                URLEncoder.encode(params.getOAuthTokenSecret(), "UTF-8");
            if ("localhost".equals(request.getServerName())) {
		// ローカル環境で動いている場合
                params.setOAuthCallback("http://localhost:8080/login" + secretToken);
            } else {
		// 本番環境(GAE)で動いている場合
		// app-idの部分は自身のサービスのアプリケーションIDに読み替えてください
                params.setOAuthCallback("http://app-id.appspot.com/login" + secretToken);
            }
        } catch (OAuthException e) {
            e.printStackTrace();
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
	// Step3 ユーザがGoogleの認証サービスにアクセスするためのURLを作成して、リダイレクト
        String requestUrl = helper.createUserAuthorizationUrl(params);
        logger.info("REQUEST URL: " + requestUrl);
        return redirect(requestUrl);
    }

}

カレンダーを表示するJSP

<%@page pageEncoding="UTF-8" isELIgnored="false"%>
<%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<%@taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt"%>
<%@taglib prefix="f" uri="http://www.slim3.org/functions"%>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<title>カレンダー</title>
</head>
<body>
<c:forEach var="e" items="${entries}">
${e.title.plainText}<br />
</c:forEach>
</body>
</html>

とりあえずこれでローカルからでも、テストできました。とはいえ、まだ理解不足のため微妙な説明かもしれないので気をつけてくださいw。どういう流れで認証ページにくれば自然なのか、一回認証したあと次回からのアクセスはどうするのが一般的なのか(または正しいのか)ってのがもうちょい触らないと見えてこない感じ。
でも、もうすこし触ってみて、前に作ったTODOリストサービスとGoogle Calendarの連携が出来るようになったらうれしいな。