Wicketで始めるオブジェクト指向ウェブ開発

第9回 WicketによるOAuth認証

この記事を読むのに必要な時間:およそ 10 分

認証手続きの実行

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()メソッドを見てください。

リスト2 アプリケーション初期化処理の一部

this.consumerKey = getInitParameter("twitter-consumer-key");
this.consumerSecret = getInitParameter("twitter-consumer-secret");
if(consumerKey == null) throw new IllegalStateException("'twitter-consumer-key' is missing in init-param of wicketFilter.");
if(consumerSecret == null) throw new IllegalStateException("'twitter-consumer-secret' is missing in init-param of wicketFilter.");

Wicketアプリケーションが起動した直後に,web.xmlからConsumerKey,ConsumerSecretを取り出し,WicketApplicationのフィールドに格納します。web.xmlの<init-param>情報は,WebApplication(WicketApplicationのスーパークラス)のgetInitParameter()メソッドによって簡単に取り出すことができます。

もしキーが登録されていなければ,アプリケーションは処理続行不可能なため,例外をスローして停止します。

RequestTokenの取得

次に,あなたのアプリケーションが登録済みであることの証明を行います。

jp.gihyo.wicket.AppSessionクラスのgetTwitterSession()メソッドを見てください。このメソッドは,Twitterオブジェクトがセッションに保存済みであれば即座にそれを返却します。まだセッションに保存されていない場合は,OAuthを使って認証を試みるために,RequestTokenを取得します。

リスト3 RequestTokenの取得

public Twitter getTwitterSession(Request request) throws NeedAuthenticationException {
  if(request == null) throw new IllegalArgumentException("'request' is missing");
  
  //既にTwitterオブジェクトを作成済みなら,認証の必要はない。
  if(twitterSession != null) return twitterSession;
  
  //AccessTokenをDBなどに永続化している場合は,ここで永続化したAccessTokenを使って
  //Twitterオブジェクトを再作成して返却すればよい。
  //このサンプルではAccessTokenを永続化していないので,すぐにOAuth認証に入る。
  
  //OAuth認証開始。RequestTokenの取得を試みる。
  Twitter client = new TwitterClient();
  client.setOAuthConsumer(consumerKey, consumerSecret);
  
  RequestToken token = null;
  try {
    token = client.getOAuthRequestToken(RequestUtils.toAbsolutePath("login"));
  } catch(TwitterException ex) {
    throw new RuntimeException(ex);
  }
  this.requestToken = token;
  this.lastAccessUrl = request.getURL();
  dirty();
  
  throw new RedirectToUrlException(token.getAuthorizationURL());
}

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ページへリダイレクトすると,次のようなアクセス許可画面が表示されます。

図3 ユーザ用の認証画面

図3 ユーザ用の認証画面

ユーザは表示されたアプリケーション名や組織情報などを確認して,自分が使っているアプリケーションであることを確認した上で「許可する」をクリックします。すると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つのクラスを作成しました。

※1
この認証処理は,アプリケーションがローカル環境で実行されていても,ファイアウォールで保護されていても,問題なく動作します。Twitterがあなたのアプリケーションにアクセスしているのではなく,リダイレクト命令を出しているだけである点に注意してください。指定したURLにアクセスするのは,Twitterではなく,あなたの使っているブラウザなのです。

著者プロフィール

矢野勉(やのつとむ)

フリーランスのプログラマ。Wicket-ja主催。

ウェブ・アプリケーションの開発を中心にさまざまな案件に関わってきました。現在はWicketによる開発を支援しています。

URLhttp://d.hatena.ne.jp/t_yano/

著書