本記事では、第8回までのサンプルプログラムとは別のプログラムを使用します。新しいサンプルプログラムをダウンロードして、ソースコードを見ながら読み進めることをお勧めします。
サンプルコードを実行する際には、web.xmlファイルを更新する必要があります。詳しくは本文をご覧ください。ファイルの更新後に第1回で紹介した「mvn jetty:run」コマンドによってアプリケーションを起動することができます。アプリケーションに接続するためのURLは「http://localhost:8080/wicket-sample-oauth/」です。
本連載はこの回をもって最終回となります。最後に取り上げるのは、リダイレクトを駆使した外部サイトとのやり取りです。
これまでの記事では、Wicket内部の世界についてのみ取り上げてきました。しかし現代のWebアプリケーションは外部のサイトとうまく連携することが重要になってきています。外部サイトと情報をやりとりするための基本的な手法がURLです。
最終回では「URL」という文字情報をWicketがどのように解釈し、Wicketの世界へと変化させているのかを取り扱います。第8回までと比べて、ずっと地道で、オブジェクト抽象化が弱い部分に触れます。
地道なプログラムですが、「WicketがURLをどのように解釈しているか」を知れば、プログラムできる範囲は大きく広がります。
それでは、OAuth認証手続きを通じて、WicketとURLの関係を見ていきましょう。
外部サイトとの連携の要はURL
Webアプリケーション間の連携に使われる方法はいくつかありますが、Webの基本ともいうべき「URL」を利用した方法は、おそらく広く使われているものです。
Twitterのような、外部サイトとの連携を考慮したWebアプリケーションは、外部サイトが利用できる機能をURLとして公開しています。ある機能を利用したければ、決められたURLに対して決められたリクエストパラメータを付けてアクセスするのです。TwitterはURLとパラメータが適切であれば、該当する機能を実行して結果を返してくれます。
Twitterの機能はすべてURLを介したAPIとして提供されている
Twitterはさまざまな機能をURLを介して提供しています。サンプルプログラムではtwitter4jを利用していたため、Javaのメソッド呼び出しだけでTwitterのさまざまな機能を利用できました。実は、これらの機能はすべて、Twitter APIとしてURLを介して提供されているものです。twitter4jはメソッド呼び出しをURLアクセスに変換してくれているのです。
URLを利用した認証仕様「OAuth」
Twitterが提供するさまざまなAPIの1つに「認証」があります。Twitterは2種類の認証APIをサポートしています。1つは、TwitterのユーザIDとパスワードを使って認証を行う方法です。
しかし、ユーザIDとパスワードを使った認証方式には問題があります。サンプルアプリケーションを、多くのユーザが利用できるWebアプリケーションとして公開した場合を考えてください。ユーザがアプリケーションを使うには、Twitterではなく、あなたのWebアプリケーションに対してTwitterのユーザIDとパスワードを提供しなければいけません。
もちろんあなたは別に他人のパスワードを盗む意図はないでしょうが、ユーザからみればリスクであることに違いはありません。そこで、ユーザにはあくまでTwitterにおいて認証を行ってもらい、あなたのアプリケーションがTwitterから「たしかにこのユーザはTwitterユーザで、あなたがアクセスすることをユーザが許可しました」という情報を受け取る仕組みが必要となりました。OAuthが実現するのは、そのような認証の仕組みです。
OAuthはTwitter独自の機能ではなく、多くのWebアプリケーションが利用している、ユーザ認証の「仕様」です。「認証」という、どのWebアプリケーションでも必要となる機能に、共通のインタフェースを定めるためのものです。Webアプリケーションが仕様に則って機能を提供することではじめて、認証が行えます。
今回使用しているライブラリtwitter4jは、Twitterが提供するOAuth認証機能もサポートしています。そのためJavaのメソッド呼び出しを利用して認証を行うことができます。
この記事ではOAuth自体については取り扱いません。OAuthについてはgihyo.jpでも「ゼロから学ぶOAuth」という記事が公開されています。是非一度目を通してみてください。
OAuth認証のためのアプリケーション登録
これ以降は、twitter4jを使ったTwitterでのOAuth認証に的を絞って説明をしていきます。
OAuthでは、認証サービス提供側(Twitter)とあなたのアプリケーションとの間での連携方法は明確に定められています。twitter4jを使ったOAuth認証では、ConsumerKey, ConsumerSecret、RequestToken、PINコード、AccessTokenの5つの情報を組み合わせて、ユーザの詐称を防ぎつつ認証を行います。
ConsumerKey, ConsumerSecretの取得
Twitterとやりとりするためには、まずはあなたのアプリケーションがTwitterの知らない謎のプログラムではなく、Twitterに登録されたアプリケーションであることを検証する必要があります。そのために使うのが、ConsumerKeyとConsumerSecretです。Twitterサイトとやり取りを行うためには、TwitterからConsumerKey、ConsumerSecretを発行してもらう必要があります。
発行と言っても、難しいことはありません。Twitterの、外部アプリケーション登録用ページに行って、必要事項を記述するだけです。登録ページは次のURLです。
Twitter画面下部にある「API」リンクをクリックし、さらに「OAuth FAQ」リンクへアクセスすることでも、上記URLのリンクを見つけることができます。
登録ページで入力した項目の大部分は、あなたのアプリケーションがユーザアカウントにアクセスすることをユーザが許可するための認証画面と、ユーザの認証済みアプリケーション一覧に表示されるものです。この内容を見て、ユーザはあなたのアプリケーションに接続許可を与えるかどうかを判断するのです。ユーザが許可を与えてくれさえすれば、あとは自由に、さもユーザがログインしたかのように、Twitterから情報を取得することができます。
ユーザが許可したアプリケーションは、Twitterの「設定」画面にある「Connections」タブで一覧することができます。
また、ユーザはいつでもあなたのアプリケーションへの許可をConnections画面から取り消すことができます。
図1 Twitterへのアプリケーション登録画面への入力内容
入力項目 | 入力する内容 |
Application Icon | ユーザの認証済みサイト一覧に表示されるアプリケーション・アイコン。選ばなければデフォルトのものが使用される |
Application Name | ユーザの認証済みサイト一覧に表示されるアプリケーション名 |
Description | どのようなアプリケーションなのかの解説文。認証画面に表示される |
Application Website | アプリケーションのWebサイトURL。ユーザの認証済みサイト一覧でアイコンをクリックすると、このURLにジャンプする。 |
Organization | アプリケーションを開発した組織やグループ名など。認証済みサイト一覧に表示される |
Site | 組織やグループのWebサイト。上記Organizationが表示された際に、指定したサイトへのリンクとして表示される |
Application Type | デスクトップクライアント(client)かWebアプリケーション(browser)のいずれかを選択する |
Callback URL | 認証成功した場合に、Twitter側からアプリケーションに認証情報を渡すためのURL。指定しなくとも、プログラムから指定することができる |
Default Access type | あなたのアプリケーションがユーザのアカウントに「読み書き(read&Write)」「読み込みのみ(Read Only)のいずれのアクセス権を必要とするかを選択する |
Use Twitter for login | 自分のサイト固有の認証機能の代替としてTwitter認証を利用したいならチェックを入れる |
登録するアプリケーションは別に本格的なアプリケーションである必要はなく、テスト的なものでもかまいません。登録はあとで解除することもできます。今回のプログラムを試すためにも、一度登録してみることをお勧めします。
登録が完了すると、http://twitter.com/oauth_clientsに、登録したばかりのアプリケーション・アイコンが表示されます。アイコンをクリックすると、あなたのアプリケーション専用のConsumerKey, ConsumerSecretが掲載されたページが表示されます。あなたのアプリケーション専用のキーがTwitterによって発行されたのです。この2つの値を使うことで、あなたのアプリケーションがTwitterに登録されたものであることを証明できます。
アプリケーションへのキー情報登録
取得したConsumerKeyとConsumerSecretは、作成中のアプリケーション専用のものです。これら2つのキーは一度発行すればそのまま使い続けることができますが、一旦解除して再発行することもできます。変化する可能性があるものですので、アプリケーション・プログラム内部に直接埋め込むのは得策ではありません。今回は、web.xmlの<init-param>要素を利用して、アプリケーションからいつでも取り出せるようにプログラムしました。2つのキー情報はどこにあってもかまいませんが、認証処理時に必要となりますので、取得しやすい場所がいいでしょう。
サンプルアプリケーションでは、web.xmlの<init-param>要素には「あなたのConsumerKey」「あなたのConsumerSecret」という値を登録しています。取得した2つの値で置き換えてください。
認証手続きの実行
2つのキーが手に入れば、いよいよプログラムからの認証手続きです。認証手続きは「RequestTokenの取得」「PINコードの取得」「AccessTokenの取得」の3段階で行います。
手続きの簡単な流れ
認証を行うためには、まずあなたのアプリケーションがTwitterに登録されているアプリケーションであることを知らせなければいけません。そのためにConsumerKeyとConsumerSecretを使います。2つの値を使ってTwitterからRequestTokenを取得します。Twitterは2つの値がTwitterによって発行されたものであること(つまりTwitterに登録されたアプリケーションであること)を確認し、RequestTokenを発行します。
次に、あなたのアプリケーションがアカウントに接続することを許可してもらうために、ユーザをTwitterが提供する認証ページへ遷移させます。
ユーザが許可を与えると、Twitterはあなたの指定したURLにリダイレクトし、PINコードと呼ばれる値を提供してきます。このコードが、ユーザがアプリケーションを許可したことの証明です。
最後に、PINコードを使って暗号化した情報をTwitterに送信し、TwitterからAccessTokenを取得します。詳細は省きますが、使用したPINコードがTwitterによって発行されたものでなければ、TwitterはAccessTokenを発行せず、認証エラーになります。
AccessTokenが、Twitterのユーザアカウントに接続するための本当のキーです。この情報を保存しておけば、以降はOAuthのやっかいな手続きを経ることなく、何度でもアカウントに接続することができます。
ConsumerKey、ConsumerSecretの取得
新しいサンプルアプリケーション「wicket-sample-oauth」を開いてください。このアプリケーションは、前回までに作成したAjaxTimelineを使ったサンプルを、OAuthに対応させたものです。
まず、jp.gihyo.wicket.WicketApplicationクラスのinit()メソッドを見てください。
Wicketアプリケーションが起動した直後に、web.xmlからConsumerKey、ConsumerSecretを取り出し、WicketApplicationのフィールドに格納します。web.xmlの<init-param>情報は、WebApplication(WicketApplicationのスーパークラス)のgetInitParameter()メソッドによって簡単に取り出すことができます。
もしキーが登録されていなければ、アプリケーションは処理続行不可能なため、例外をスローして停止します。
RequestTokenの取得
次に、あなたのアプリケーションが登録済みであることの証明を行います。
jp.gihyo.wicket.AppSessionクラスのgetTwitterSession()メソッドを見てください。このメソッドは、Twitterオブジェクトがセッションに保存済みであれば即座にそれを返却します。まだセッションに保存されていない場合は、OAuthを使って認証を試みるために、RequestTokenを取得します。
RequestTokenを取得するためには、まずsetOAuthConsumer()メソッドでConsumerKeyとConsumerSecretをTwitterオブジェクトに設定します。その後にgetOAuthRequestToken()メソッドを呼び出すことで、TwitterからRequestTokenが発行されます。getOAuthRequestToken()メソッドは引数を1つ受け取ります。この引数は、OAuth認証でユーザの許可が行われた後にリダイレクトしてほしいURLです。外部サイトであるTwitterからリダイレクトするためのものなので、絶対パスでURLを渡す必要があります。
WicketのRequestUtilsクラスには、アプリケーション内部の相対パスを絶対パスに変換するためのユーティリティメソッドtoAbsolutePath()があります。このメソッドで「login」というアプリケーション内でのみ有効な相対URLを、「http://example.com/wicket-sample-oauth/login」のような絶対パスに変換することができます。
RequestTokenを取得すると、次に、ユーザをTwitterの認証用画面に強制的に遷移させる必要があります。ブラウザにリダイレクト命令を返すのです。リダイレクトしてしまうとユーザがもともとアクセスしていたページの表示はキャンセルされてしまいます。後ほどもともとアクセスしていたページに再アクセスするために、アクセス中URLをセッションに保存します。Wicketのorg.apache.wicket.RequestクラスにあるgetURL()を呼び出すと、現在アクセス中のURLを取り出すことができます。
取得したRequestTokenは、後ほど利用するために、セッションのフィールドに保存しておきます。
セッション内の情報を更新した場合には、忘れずにdirty()を呼び出しましょう。この呼び出しにより、Wicketのセッションがアプリケーション・サーバの管理するHttpSessionと間違いなく同期されます。
最後に、リダイレクトを行います。
Wicketでのリダイレクト方法はいくつかありますが、最も簡単が方法がRedirectToUrlException例外をスローすることです。どのようなクラスからでも、この例外をスローすれば、Wicketは処理を途中でやめて指定されたURLにリダイレクトします。例外により処理を途中で中断できるので、ある条件であればページの表示をやめて別のURLに移動する、という時に便利に使えます。
Twitterからのリダイレクトを受け付ける
Twitterページへリダイレクトすると、次のようなアクセス許可画面が表示されます。
ユーザは表示されたアプリケーション名や組織情報などを確認して、自分が使っているアプリケーションであることを確認した上で「許可する」をクリックします。するとTwitterは、RequestToken取得時にあらかじめ指定しておいたURLへと、ユーザをリダイレクトさせます。今回のプログラムであれば、サンプルアプリケーションの「login」というURLにアクセスするようにブラウザへリダイレクト命令を返します[1]。
「login」というURLにアクセスされたら、AppSession内にTwitterオブジェクトを生成します。「login」にアクセスすることによって動作するプログラムは、ページではない点に注意してください。「login」プログラムはただTwitterオブジェクトを作成するだけで、処理が済めば、ユーザがもともとアクセスしようとしていたURLにリダイレクトします。
このような、ページではない「何も表示せずに処理を行うだけのプログラム」をWicketで扱う方法が必要です。もちろん、表示するものがない場合でもページクラスを使うことは出来ます。ページ生成処理途中でリダイレクトしてしまえばいいわけです。しかし、ページは必ずHTMLファイルと対で作る必要があります。何も表示しないものの、とりあえずHTMLファイルだけは用意する必要があるのです。今回は敢えて、ページを使わずに実現する方法を採用してみます。
では、Servletはどうでしょうか? web.xmlを使えば、Servletを特定のURLに結びつけることができます。しかし、ServletではWicketのAppSessionにアクセスすることが難しくなります。あくまでWicket内部で行いたいところです。
そのための方法が「リクエスト・ターゲット」です。
Wicketにおいて、URLでアクセス可能なすべてのリソースは必ず「リクエスト・ターゲット」に紐づけられています。リクエスト・ターゲットとはIRequestTargetインタフェースを実装したクラスです。たとえばページは、Wicket内部ではPageRequestTargetというリクエスト・ターゲットを使って生成されています。
さらに、IRequestTargetUrlCodingStrategyというインタフェースを実装したオブジェクトが、URLとリクエスト・ターゲットの相互変換を担います。
IRequestTargetとIRequestTargetUrlCodingStrategyを実装したクラスを独自に作ることで、どのようなURLアクセスでもWicketで扱うことができます。
今回はjp.gihyo.wicket.LoginProcessor、jp.gihyo.wicket.LoginProcessorUrlCodingStrategyという2つのクラスを作成しました。
LoginProcessorUrlCodingStrategyを実装する
IRequestTargetUrlCodingStrategyの実装クラスを直接作るのは骨の折れる作業です。そのため、実際にはAbstractRequestTargetUrlCodingStrategyクラスを継承するほうが便利です。このクラスを継承すれば、リクエスト・ターゲットをURL文字列に変換するencode()、RequestParametersオブジェクトを元にリクエスト・ターゲットを生成するdecode()、あるリクエスト・ターゲットがそのIRequestTargetUrlCodingStrategy実装で処理可能かどうかを判定するmatches()の3つのメソッドを実装することで、きちんと機能するクラスを作成できます。
また、IRequestTargetUrlCodingStrategyは、ページと同じように、マウントすることによってURLと結びつけられます。結びつけられたURL情報は、AbstractRequestTargetUrlCodingStrategyのgetMountPath()メソッドによって取り出すことができます。
典型的な実装は次のようになります。LoginProcessorUrlCodingStrategyの実際のプログラムです。
decode()メソッドとmatches()メソッドはたった1行ですから分かりやすいでしょう。encode()は若干長いですが、重要なコードは3行だけです。AppendingStringBufferオブジェクト(Wicketの提供する、StringBuilderと似たクラス)を作成し、getMountPath()で取り出した、マウント先URLをまず設定します。さらにAbstractRequestTargetUrlCodingStrategyクラスの提供するappendParameters()メソッドをつかって、AppendingStringBufferにパラメータを追加します。encode()メソッドの実装はおおむねこのクラスに近い実装になることが多いでしょう。
このクラスによって、URLへのアクセスはLoginProcessorへのアクセスに変換されます。
LoginProcessorを実装する
LoginProcessorの実装するIRequestTargetインタフェースには、リクエストを実際に処理するrespond()メソッドと、リクエスト処理後の解放処理を行うためのdetach()メソッドが定義されています。この2つを実装すればいいのです。今回は解放処理が必要ないため、detach()メソッドの中身は空のままです。
LoginProcessorのコンストラクタは、LoginProcessorUrlCodingStrategyからRequestParametersオブジェクトを受け取り、フィールドに格納します。RequestParametersは、その名の通り、URLに含まれるパラメータを表すオブジェクトです。URLの生の情報ではなく、URLが表すWicketコンポーネントのIDなど、より細かな、具体的な情報が格納されています。
URLのパラメータ値はgetParameters()メソッドで取り出すことができます。パラメータ・キーと値が格納されたMapが返却されます。
Twitterの認証画面からリダイレクトされた際に、パラメータには「PINコード」と呼ばれる値が埋め込まれています。LoginProcessorはこのPINコードを使って処理を行います。
実際にアクセスを処理するのはrespond()メソッドです。respond()メソッドでは、getParameters()メソッドで取り出したMapから「oauth_verifier」というパラメータを取り出します。これが「PINコード」と呼ばれている値です。
Mapから取り出した値は、Stringではなく、Stringの配列である点に注意してください。リクエスト・パラメータは1つのキーに対して複数の値を持つことができるため、値は配列となっています。oauth_verifierについては値は常に1つなので、配列の1つ目の要素だけを取り出します。
PINコードが手に入れば、あとの処理はすべてAppSessionで行います。AppSessionのloginを呼び出すことで、セッション内部にTwitterオブジェクトを生成します。
AppSessionのlogin()メソッドの実装についてはこの後すぐに見ます。ここでは、Twitterオブジェクトの生成が成功すればtrueが返るということだけを知っておけばかまいません。ログインが成功すれば、セッションに保存しておいた、ユーザがもともとアクセスしようとしていたURLへとリダイレクトします。
AccessTokenの取得とTwitterオブジェクトの生成
PINコードを受け取ったということは、ユーザがアプリケーションからのアクセスを許可したということです。いよいよ、最後の値であるAccessTokenを取得します。そのためには、いままでに使ったConsumerKey, ConsumerSecret, RequestToken、PINコードの4つの値すべてが必要になります。
getOAuthAccessToken()メソッドがAccessTokenの取得を行っています。直前にsetOAuthConsumer()を使ってConsumerKeyとConsumerSecretをTwitterオブジェクトにセットしていることにも注目してください。getOAuthAccessToken()にはRequestTokenと、先ほどLoginProcessorで取得したPINコードを渡します。AccessToken取得段階では、いままでに登場した4つの情報(ConsumerKey, ConsumerSecret, RequestToken, PINコード)がすべて登場しています。この4つが正しくなければAccessTokenは発行されません。AccessTokenというのはそれほど厳しく管理しなければいけない情報なのです。
AccessTokenが取得できれば、setOAuthAccessToken()メソッドを使って、TwitterオブジェクトにAccessTokenをセットします。AccessTokenがセットされたTwitterオブジェクトでは、許可を出したユーザでログインしたかのようにTwitterにアクセスすることができます。
これで認証処理は完了です。
AccessTokenの保存
AccessTokenは(少なくとも現状のTwitterでは)発行後に期限切れすることがありません。ということは、AccessTokenをデータベースなどに永続化しておけば、次回は認証の手続きを踏むことなく永続化したAccessTokenを使ってTwitterオブジェクトを生成できるということです。
そのため、AccessTokenの漏洩には特に注意しなければなりません。AccessTokenさえ手に入れば、ユーザ認証を経ることなくTwitterアカウントにアクセスできるのです。リクエスト・パラメータやCookieを介してAccessTokenを扱うのは危険でしょう。また、ログインせずにアクセスできるところにAccessTokenを保存するのも危険でしょう。
データベースにAccessTokenを保存した場合は、あなたのアプリケーションにログインしない限りはAccessToken情報を取り出せないようにしたほうが良いでしょう。
AccessTokenはユーザがいつでも無効にすることができます。ユーザがアプリケーションへの許可を取り消すと、取得したAccessTokenではTwitterにアクセスできなくなります。今回のプログラムでは許可が取り消された場合の処理は行っていないので、取り消し後の処理はすべてエラーになってしまいます。
皆さんの手で、許可が取り消されてエラーになった場合にはOAuth認証を再度行うように、プログラムを作り変えてみてください。
ヒントを1つだけ。認証失敗した場合にはTwitterExceptionがスローされ、TwitterExceptionのgetStatusCode()メソッドはHTTPステータスコード401(Unauthorized)を返します。
LoginProcessorとURLを結びつける
TwitterをLoginProcessorにリダイレクトさせるには、LoginProcessorをURLと結びつけ、外部からアクセス可能にしなければなりません。
方法は簡単です。LoginProcessorを生成するLoginProcessorUrlCodingStrategyを、ページと同じようにURLに「マウント」します。
すべてのIRequestTargetUrlCodingStrategyインタフェース実装クラスは、Applicationのmount()メソッドによってURLと結びつけることができます。ページをURLと結びつけるmountBookmarkablePage()メソッドは、内部ではBookmarkablePageRequestTargetUrlCodingStrategyというIRequestTargetUrlCodingStrategy実装クラスをマウントしています。ページも、IRequestTargetUrlCodingStrategyとIRequestTargetを使って処理されているのです。
WicketのURL制御にはほかにもいくつか機能がありますが、もっとも基本となるのは、これら2つのインタフェースです。すべてのアクセスは一旦これら2つのインタフェースを経由します。
ページに何かを表示するならばPageやPanelを、URLアクセスに対してデータを返却するならば、第5回で使用したWebResourceを使うと良いでしょう。それらでは取り扱えない、「URLアクセスによってただプログラムが動く」というケースでは、今回のように独自のIRequestTargetUrlCodingStrategyとIRequestTargetを作成することで対応することができます。
Webアプリケーションが外部とやり取りする最も基本的な要素は、URLです。IRequestTargetUrlCodingStrategyとIRequestTargetは、URLという文字情報と、Wicketのオブジェクトの世界の橋渡しを行います。この2つはURLを直接扱うので、Wicketらしいオブジェクトをベースにしたプログラムに比べて、幾分抽象化されていない印象を受けるでしょう。この2つのインタフェースが、外部とWicketの世界の境界だからです。
2つのインタフェースの使い方を理解すれば、Wicketでできることがぐっと広がるでしょう。
さらにWicketを知る
この連載では、Wicketのキーとなる要素について、Twitterアプリケーションを実装する中で触れてきました。かなり広範囲の概念を紹介しましたが、記事の文字数の関係から、個々の概念を深く掘り下げることはできていません。
個々の概念について、Wicketが提供する機能を知りたいのであれば、本記事からもリンクされている拙著をお読みいただければと思います。
Wicketでアプリケーションを作成している中で疑問が出てきて質問したくなった場合は、Wicketユーザグループ(wicket-ja)のメーリングリストに質問を投稿すると良いでしょう。
本連載でWicketに興味を持っていただければ幸いです。
それでは、Wicketでプログラミングを楽しんでください。