この記事を読む前に、サービス・プロバイダー上の保護されたユーザー・リソースにコンシューマー・サイトからアクセスする上で、OAuth が優れた手段であることを理解しておく必要があります。OAuth が優れているのは、OAuth を利用すれば、ユーザーのデータが元々保持されている場所以外のサイトにクレデンシャルが公開されることはないからです。第 1 回で作成したデスクトップ Twitter クライアントを利用すると、Twitter に保持されているユーザー・データへのアクセス権を MyTtDesktopClient に付与することにより、Twitter の近況をアップデートすることができます。同じユーザーが再度アクセスする場合にはアクセス・トークンを再利用できるため、ユーザー・エクスペリエンスが向上されます。
この記事では、OAuth を使用する Web 版の Twitter クライアントを作成する方法を説明します。目標は、Twitter に対するユーザー認証を OAuth を使って行った後でユーザーの近況をアップデートすることができる、Web アプリケーションを作成することです。このアプリケーションは友だちのタイムラインを表示できるようにもする予定であり、優れたマッシュアップ・サイトを構築する際のベースとすることもできます。
MyTtDesktopClient の場合と同じように、この Web 版 Twitter クライアントではユーザーが自分の Twitter の近況をアップデートすることができます。さらに、この Web 版 Twitter クライアントでは、ユーザーの最新の近況を表示したり、ユーザーが自分の近況を削除したりすることもできます。またこの Web アプリケーションでは、OAuth 認証の成功後に Twitter によってリダイレクトされる宛先をコールバック URL として設定することができます。こうするとブラウザーを localhost にリダイレクトできるため、開発の際に非常に便利です。また、OAuth 認証が前回と少し異なることにも注意してください。それは、今回のアプリケーションはデスクトップ・アプリケーションではなくブラウザー・アプリケーションとして登録されるためです。
Web アプリケーション (MyTtWebClient) を Twitter に登録する
今回も前回と同じく、Twitter で OAuth を使う前に、この Web アプリケーションを http://twitter.com/oauth_clients に登録する必要があります。あるいは場合によっては、デスクトップ Twitter クライアントとして登録したアプリケーションを変更することもできます。ここでは新しいアプリケーションを以下のように Twitter に登録します。
- Application Name (アプリケーション名): MyTtWebClient
- Description (説明): A Web Twitter client using OAuth (OAuth を使用する Web 版 Twitter クライアント)
- Application Web site (アプリケーションの Web サイト): ここにアプリケーションのホームページを入力します。
- Application Type (アプリケーションのタイプ): Browser - ここでは Web アプリケーションを作成します。
- Callback URL (コールバック URL): ここにアプリケーションのコールバック URL を入力します。
localhostでは動作しません。 - Default Access type (デフォルトのアクセス・タイプ): Read & Write - ユーザーが書き込みアクセスできるようにします。
- Use Twitter for login (ログインに Twitter を使う): Yes - ここでは認証に Twitter を使います。
今回はアプリケーションのタイプが第 1 回の Client とは異なり、Browser であることに注意してください。この場合も Read & Write (読み書き) アクセスを許可することを忘れないでください。また、「Use Twitter for login (ログインに Twitter を使う)」チェックボックスも Yes にチェックを入れます。
アプリケーションを適切に登録できると、コンシューマー・キー、コンシューマー・シークレット、そして 3 つの URL (リクエスト・トークンの URL、アクセス・トークンの URL、承認用の URL) が取得されます。コンシューマー・キーとコンシューマー・シークレットは WEB-INF/Web.xml の中で設定されます。
コードを読まずに Web アプリケーションをコンパイルして実行したい場合には (ソース・コードは下記の「ダウンロード」セクションから入手することができます)、Web.xml の中に皆さんがお持ちのコンシューマー・キーとコンシューマー・シークレットを設定する必要があります。WEB-INF/lib の下に以下のライブラリー・ファイルを追加します。
commons-logging-1.1.1.jarlog4j-1.2.15.jartwitter4j-2.0.9.jar
ここで、Twitter4J はバージョン 2.0.9 またはそれ以降を使用することに注意してください。その理由は、コールバック URL のカスタマイズがサポートされているのは 2.0.9 バージョンまたはそれ以降の Twitter4J のみだからです。またデフォルトで、コールバック URL は Web.xml の中で構成され、localhost を指します。(Twitter4J は TwitterAPI 用のオープンソースの Java™ ライブラリーです。「参考文献」を参照)。このアプリケーションを MyTtWebClient.war としてコンパイルしてアセンブルし、Tomcat にデプロイし、http://localhost:8080/MyTtWebClient にアクセスしてみます。
MyTtWebClient の中心は MyTwitterServlet というサーブレットです (完全なソース・コードは下記の「ダウンロード」セクションにあります)。このサーブレットは myttWebclient.MyTwitterServlet クラスの中で定義されます。doPost(HttpServletRequest request, HttpServletResponse response) メソッドは、OAuth 認証、ユーザーのTwitter の近況のアップデートと削除、そして友だちのタイムラインの表示を行います。
doPost(...) メソッドは、OAuth 認証、そして login_twitter.html と update_twitter_status.jsp とのさまざまな対話動作を行います。このアプリケーションのウェルカム・ページ (login_twitter.html) はユーザーが Twitter にサインインするためのボタンを表示します (図 1)。ユーザーがこのボタンをクリックすると、doPost(...) メソッドが呼び出されます。ボタンをクリックするユーザーには、新規のユーザーと以前に認証されたことがあるユーザーの 2 種類があります。リスト 1 を見るとわかるように、このコードはユーザーの Twitter ID をクッキーからロードしようとします。まだクッキーがロードされていない場合には、そのユーザーは新規のユーザーとして扱われます。Twitter からリクエスト・トークンが要求されると、ユーザーは Twitter にリダイレクトされ、ユーザーの Twitter データに対する読み書きアクセス権をこのコードに付与するための承認が行われます (図 2)。すべてが順調に進むと、Twitter は Web.xml で設定されるコールバック URL にユーザーをリダイレクトするはずです。ここではコールバック URL が MyTtServlet というサーブレットを指すように設定しています。今度は、承認済みのリクエスト・トークンが Twitter によってアクセス・トークンと交換されます。アクセス・トークンが取得できると、そのアクセス・トークンは WEB-INF/token.txt というファイルに保存され、今後のアクセスに使われます。実際のアプリケーションでは、おそらくアクセス・トークンをデータベースに保存するはずです。また、クッキーをユーザーのブラウザーに保存しておき、同じユーザーが再度アクセスした場合のために、以前に認証されたことがあるユーザーからのアクセスかどうかをチェックできるようにするはずです。認証が終わると、ユーザーは update_twitter_status.jsp にリダイレクトされます。サインインに成功した場合のページを示したものが図 3 です。図 3 では、ユーザーの最新の近況アップデートと、そのユーザーの友だちのタイムラインの一部が表示されています。
リスト 1. Twitter で OAuth 認証を行う
protected void doPost(HttpServletRequest request,
HttpServletResponse response) throws ServletException, IOException {
HttpSession session = request.getSession();
// Call back from Twitter
String oauthToken = request.getParameter(PARAM_OAUTH_TOKEN);
if (oauthToken != null) {
logger.debug(PARAM_OAUTH_TOKEN + " received from Twitter");
try {
Twitter twitter = (Twitter) session.getAttribute(ATTR_TWITTER);
RequestToken requestToken = (RequestToken) session
.getAttribute(ATTR_REQUEST_TOKEN);
AccessToken accessToken;
if (callbackUrl == null) {
accessToken = twitter.getOAuthAccessToken(requestToken);
} else {
String oauthVerifier = request
.getParameter(PARAM_OAUTH_VERIFIER);
logger.debug(PARAM_OAUTH_VERIFIER
+ " received from Twitter");
accessToken = twitter.getOAuthAccessToken(requestToken
.getToken(), requestToken.getTokenSecret(),
oauthVerifier);
}
twitter.setOAuthAccessToken(accessToken);
session.removeAttribute(ATTR_REQUEST_TOKEN);
session.setAttribute(ATTR_TWITTER, twitter);
int id = twitter.verifyCredentials().getId();
logger.debug("Access token retrieved for user " + id
+ " from Twitter");
storeAccessToken(id, accessToken);
Cookie cookie = new Cookie(COOKIE_TWITTER_ID, "" + id);
cookie.setMaxAge(63072000); // Valid for 2 years
response.addCookie(cookie);
logger.debug("Cookie set for user " + id);
// Get last status and friends' timelines
getMyLastStatusAndStoreInSession(session);
getFriendsTimelinesAndStoreInSession(session);
// Go to the update status page
request.getRequestDispatcher(PAGE_UPDATE_STATUS).forward(
request, response);
} catch (TwitterException e) {
logger.error("Failed to retrieve access token - "
+ e.getMessage());
throw new ServletException(e);
}
}
// Actions within this application
String action = request.getParameter(PARAM_ACTION);
if (ACTION_SIGN_IN.equals(action)) {
logger.debug("Signing in with Twitter...");
Twitter twitter = new Twitter();
twitter.setOAuthConsumer(consumerKey, consumerSecret);
// Try to load Twitter ID from cookies
String id = null;
Cookie[] cookies = request.getCookies();
if (cookies != null) {
Cookie cookie;
for (int i = 0; i < cookies.length; i++) {
cookie = cookies[i];
if (COOKIE_TWITTER_ID.equals(cookie.getName())) {
id = cookie.getValue();
}
}
}
// Try to load access token if user's Twitter ID is retrieved
AccessToken accessToken = null;
if (id != null) {
accessToken = loadAccessToken(id);
if (accessToken != null) {
twitter.setOAuthAccessToken(accessToken);
session.setAttribute(ATTR_TWITTER, twitter);
// Get last status and friends' timelines
try {
getMyLastStatusAndStoreInSession(session);
getFriendsTimelinesAndStoreInSession(
session, true);
} catch (TwitterException e) {
e.printStackTrace();
}
// Access token loaded, go the up update status page
logger.debug("Going to the status update page...");
request.getRequestDispatcher(PAGE_UPDATE_STATUS).forward(
request, response);
}
}
// Can not load access token, go to Twitter for authentication
if (accessToken == null) {
try {
RequestToken requestToken;
if (callbackUrl == null) {
requestToken = twitter.getOAuthRequestToken();
} else {
requestToken =
twitter.getOAuthRequestToken(callbackUrl);
}
String authorisationUrl = requestToken
.getAuthorizationURL();
session.setAttribute(ATTR_TWITTER, twitter);
session.setAttribute(ATTR_REQUEST_TOKEN, requestToken);
logger.debug("Redirecting user to " + authorisationUrl);
response.sendRedirect(authorisationUrl);
} catch (TwitterException e) {
logger.error("Sign in with Twitter failed - "
+ e.getMessage());
throw new ServletException(e);
}
}
} else if (ACTION_UPDATE.equals(action)) {
// Handle ACTION_UPDATE, ACTION_DELETE, ACTION_MORE and ACTION_LATEST
......
}
|
ユーザーがサインイン・ボタンをクリックした後、MyTwitterServlet がユーザーのクッキーをロードできる場合には、このユーザーは以前に認証されたことがあるユーザーとして扱われます。リスト 1 を見るとわかるように、ユーザーのアクセス・トークンは token.txt から取得しています。従って、このユーザーを再度認証するために Twitter にアクセスする必要はありません。このユーザーの最新の近況と何人かの友だちのタイムラインを Twitter から取得できると、このユーザーは updatee_twitter_status.jsp にリダイレクトされます (図 3)。
この連載の第 1 回の MyTtDesktopClient を試した方は、Twitter データへのアクセスを許可した後、Twitter から返された PIN を入力する必要があったことを思い出してください。今回はサーブレットによるリダイレクトを使用できるため、登録の際にアプリケーションのスタイルとして Browser を選択しました。そのため、より適切なユーザー・エクスペリエンスが実現されています。
図 1. MyTtWebClient のウェルカム・ページ
図 2. MyTtWebClient に対してアクセス権を付与する
図 3. OAuth 認証された後の MyTtWebClient
doPost(...) メソッドの残りの部分をリスト 2 に示します。このコード部分は、図 3 に表示されている 3 つのボタンのアクションと、ユーザーの最新のアップデートの最後にある削除リンクに対応しています。これらのボタンやリンクを使うことで、ユーザーは自分の最新のアップデートを削除することや、近況をアップデートすること、そして友だちのタイムラインを見ることができます。このコードを改善する方法としては、例えばこのアプリケーションに GWT (Google Web Toolkit) を使って Ajax を適用し、ユーザー・エクスペリエンスを向上させる方法がありますが、それは皆さんへの宿題とすることにします。図 4 は近況のアップデートが適切に行われた状態を示すスクリーン・ショットです。
リスト 2.
myttWebclient.MyTwitterServlet でサポートされているアクション
protected void doPost(HttpServletRequest request,
HttpServletResponse response) throws ServletException, IOException {
HttpSession session = request.getSession();
// OAuth authentication support
......
} else if (ACTION_UPDATE.equals(action)) {
logger.debug("Updating Twitter status...");
String status = request.getParameter(PARAM_STATUS);
Twitter twitter = (Twitter) session.getAttribute(ATTR_TWITTER);
try {
Status twitterStatus = twitter.updateStatus(status);
logger.debug("Successfully updated the status to ["
+ twitterStatus.getText() + "].");
// Update last status
session.setAttribute(ATTR_LAST_UPDATED_STATUS, twitterStatus
.getText()
+ " " + twitterStatus.getCreatedAt());
session.setAttribute(ATTR_LAST_UPDATED_STATUS_ID, ""
+ twitterStatus.getId());
// Update friends' timelines
getFriendsTimelinesAndStoreInSession(session, true);
// Stay in the update status page
logger.debug("Staying in the status update page...");
request.getRequestDispatcher(PAGE_UPDATE_STATUS).forward(
request, response);
} catch (TwitterException e) {
logger.error("Failed to update Twitter status - "
+ e.getMessage());
throw new ServletException(e);
}
} else if (ACTION_DELETE.equals(action)) {
logger.debug("Deleting Twitter status...");
String strId = (String) session
.getAttribute(ATTR_LAST_UPDATED_STATUS_ID);
if (strId != null && strId != "") {
Twitter twitter = (Twitter) session.getAttribute(ATTR_TWITTER);
try {
int id = twitter.verifyCredentials().getId();
twitter.destroyStatus(Long.parseLong(strId));
session.removeAttribute(ATTR_LAST_UPDATED_STATUS);
session.removeAttribute(ATTR_LAST_UPDATED_STATUS_ID);
logger.debug("Last update deleted for Twitter user " + id
+ " deleted, session record removed");
// Get last status and friends' timelines
getMyLastStatusAndStoreInSession(session);
getFriendsTimelinesAndStoreInSession(session, true);
} catch (TwitterException e) {
logger.error("Failed to delete Twitter status - "
+ e.getMessage());
throw new ServletException(e);
}
}
// Stay in the update status page
logger.debug("Staying in the status update page...");
request.getRequestDispatcher(PAGE_UPDATE_STATUS).forward(request,
response);
} else if (ACTION_MORE.equals(action)) {
// Update friends' timelines
getFriendsTimelinesAndStoreInSession(session);
// Stay in the update status page
logger.debug("Staying in the status update page...");
request.getRequestDispatcher(PAGE_UPDATE_STATUS).forward(request,
response);
} else if (ACTION_LATEST.equals(action)) {
// Update friends' latest timelines
getFriendsTimelinesAndStoreInSession(session, true);
// Stay in the update status page
logger.debug("Staying in the status update page...");
request.getRequestDispatcher(PAGE_UPDATE_STATUS).forward(request,
response);
}
}
|
図 4.
MyTtWebClient によってアップデートされた Twitter での近況
これで MyTtWebClient の全体像を理解できたことと思います。MyTtWebClient を Web サーバーにデプロイする場合には、コールバックをサポートする必要はありません。コールバックを無効にするためには、単純に WEB-INF/Web.xml の中で callbackUrl パラメーターを空のストリングにします。ただし、OAuth 認証が成功した後のリダイレクト先を Twitter が認識できるように、適切なコールバック URL を Twitter に設定する必要があることを忘れないでください。
この Web アプリケーションを本番環境で使用する場合、Twitter には API の呼び出しに制限があることに注意する必要があります。例えば、REST API 呼び出しに対する回数制限はデフォルトで 1 時間当たり 150 リクエストです。幸いなことに、Twitter では 1 時間当たり 2 万回、リクエストを送信することができます。詳細については Twitter の FAQ を参照してください (「参考文献」を参照)。
この連載第 2 回目の今回は、MyTtWebClient という Web 版 Twitter クライアントの作成方法を説明しました。この Web アプリケーションをとおして、OAuth が Web ブラウザーとシームレスに動作することを示しました。また MyTtWebClient には MyTtDesktopClient よりも多くの機能を追加し、例えば最新のアップデートを削除したり友だちのタイムラインを表示したりできるようにしました。第 3 回では、この Web 版 Twitter クライアントを Google App Engine にマイグレートする方法について説明します。
| 内容 | ファイル名 | サイズ | ダウンロード形式 |
|---|---|---|---|
| Source code | oauth-part2-source-code.zip | 16KB | HTTP |
学ぶために
- OAuth Core 1.0 Revision A には OAuth の最新仕様が解説されています。
- 初心者用の OAuth 解説記事として、Beginner's Guide to OAuth - Part I: Overview (概要)、Beginner's Guide to OAuth - Part II : Protocol Workflow (プロトコルのワークフロー)、Beginner's Guide to OAuth - Part III : Security Architecture (セキュリティーのアーキテクチャー) を読んでください。
- Twitter4J は TwitterAPI 用の Java ライブラリーです。
- Google App Engine を利用すると、Google のインフラ上で Web アプリケーションを実行することができます。
- Google Web Toolkit を利用すると、Java プログラミング言語で Ajax フロント・エンドを作成することができます。
- Google Plugin for Eclipse を利用すると、GWT アプリケーションや GAE アプリケーションを効率的に作成することができます。
- 「App Engine での JPA の使用」は Google App Engine でのJPA (Java Persistence API) の使い方を解説しています。
- Twitter の FAQ のページを訪れ、Twitter の使い方とプロトコルについて学んでください。
- My developerWorks で developerWorks のエクスペリエンスをパーソナライズしてください。
製品や技術を入手するために
- IBM 製品の評価版をダウンロードするか、あるいは IBM SOA Sandbox のオンライン試用版で、DB2®、Lotus®、Rational®、Tivoli®、WebSphere® などが提供するアプリケーション開発ツールやミドルウェア製品を試してみてください。
