みなさん、こんにちは。 7月にウノウに入社しました細川です。
私は、これまでいろいろなオープンソースの恩恵にあずかってきましたが、こちらから貢献をしたことは、ほとんどありませんでした。この記事がお役に立てれば幸いです。
ウノウ入社前にはJavaを主に使っていましたので、今回は、JavaのOAuthライブラリを使う方法について書いてみたいと思います。
OAuthとは
OAuthは、セキュアな認証手段として多く使われてきています。 twitterもweb APIの認証手段として採用しています。 OAuthを使った認証を行うことで、ユーザーはアカウントやパスワードを知られることなく、第三者サービスにAPIの使用を許可することができます。また、その認証は第三者サービスに関係なく取り消すことができます。
OAuth Community http://oauth.net/
OAuthコミュニティが各種プラットフォーム向けのライブラリを公開していますので、今回は、Javaのライブラリをビルドして使用します。
Code - OAuth http://oauth.net/code/
Repository http://oauth.googlecode.com/svn/code/
OAuth Library をビルドする
Eclipseを使ったビルドを紹介します。
新規プロジェクトを生成し、ショートカットメニューから「インポート」を選択します
SVNからプロジェクトを選択」を選択し、次へ
ロケーションにhttp://oauth.googlecode.com/svn/code/ を指定します。
フォルダ「java」を選択します。
チェックアウトオプションは上画像のように選択してください。
チェックアウトできましたら、次にプロジェクトの設定を行います。
Default output folderに「oauth/bin」を設定します。
また、Sourceタブから上画像のようにフォルダをパスに設定します。
LibrariesタブからSVNからチェックアウトしたlibフォルダ以下にあるライブラリをビルドパスに追加します。
これで、ライブラリがビルドされるはずです。
このままですと、classファイルがばらばらになっている状態ですので、Fat Jar Eclipse Plug-Inを使って、Jarファイルにアーカイブしましょう。インストールはこちらから。
Fat-Jarは、実行形式のJar ファイルを簡単に作れたり、参照する外部JarファイルライブラリもJar内に配置できたりする優れものですが、今回は、単純なアーカイブを作成します。
Fat-Jarをインストールしたら、プロジェクト上でショートカットメニューを開き、「Build Fat Jar」を選択します。
Jarアーカイブの名前を設定して「Finish」で作成されます。
OAuthライブラリを使う
今回は、Twitterを例にとって、サーブレットから認証、APIアクセスを行うこととします。
Twitterのアカウントをもっている方なら、Twitterアプリケーション からアプリケーションを登録することができますので、実際に試したい場合には登録してください。
登録が完了すると、アプリケーションがOAuth認証に必要とする2つのキーと認証のためにアクセスする3つのURLが手に入ります。
これらの提供された値とOAuthライブラリの主に4つのクラスを用いて認証を行い、Twitter APIにアクセスしてみましょう。
4つのクラス
net.oauth.client.OAuthClient
通信を行うクラス
実際に使用されるHTTP通信の実装をラップします。
net.oauth.OAuthServiceProvider
プロバイダを定義しているクラス
プロバイダから提供されるURLをラップします。
net.oauth.OAuthConsumer
コンシューマ(第三者サービス)を定義しているクラス
コンシューマ・キー、コンシューマ・シークレット、コールバックURLなど、コンシューマに結びつく値を扱います。
net.oauth.OAuthAccessor
アクセスを定義しているクラス
アクセス・トークン、リクエスト・トークン、トークン・シークレットなど、個々の認証に関わる値を扱います。
認証URLを生成する
OAuth認証において認証はTwitter(プロバイダ)が行い、その結果がリダイレクトによりサイト(コンシューマ)に通知されます。
ですから、まず、認証先へのURLを生成し、それをユーザーにクリックしてもらうか、リダイレクトして認証先に移動させる必要があります。以下に、リダイレクトにより認証先に飛ばすサーブレットのdoGetメソッドを示します。
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
String destUrl = request.getParameter("dest");
OAuthClient client = new OAuthClient( new URLConnectionClient());
OAuthServiceProvider provider = new OAuthServiceProvider( REQUEST_TOKEN_URL, AUTHORIZE_URL, ACCESS_TOKEN_URL);
OAuthConsumer consumer = new OAuthConsumer( CALL_BACK_URL,
CONSUMER_KEY, COMSUMER_SECRET, provider);
OAuthAccessor accessor = new OAuthAccessor( consumer);
String redirectTo = null;
try{
try {
//get request token first from Twitter.com
HashMap params = new HashMap();
params.put( "oauth_callback",
OAuth.addParameters(accessor.consumer.callbackURL,
"dest", destUrl));
//get request token first from Twitter.com
client.getRequestToken(accessor, null, params.entrySet());
} catch (OAuthException e) {
throw new OperationFailedException( "It failed to authenticate Twitter account", e);
} catch (URISyntaxException e) {
throw new OperationFailedException( "It failed to authenticate Twitter account", e);
}
//build redirect path to twitter authentication page
redirectTo = OAuth.addParameters(
accessor.consumer.serviceProvider.userAuthorizationURL,//auth URL(twitter.com)
"oauth_token", accessor.requestToken//
);
} catch (IOException e) {
throw new RuntimeException( "It failed to authenticate Twitter account", e);
}
response.sendRedirect( redirectTo);
}
最初の部分ですが、ここでは、OAuthClientをもっとも単純なURLConnectionクラスを使うように初期化しています。
OAuthClient client = new OAuthClient( new URLConnectionClient());Jakarta Commons HttpClientのv3やv4を使用することもできます。その場合にはそれぞれnet.oauth.client.HttpClient3、net.oauth.client.HttpClient4クラスを使って初期化します。しかし、Google App Engine for Java環境では、スレッドが使えない関係で、URLConnectionClientしか動作しません。
次に、OAuthライブラリの各クラスをTwitter(プロバイダ)から提供されたパラメータを使って初期化しています。
OAuthServiceProvider provider = new OAuthServiceProvider( REQUEST_TOKEN_URL, AUTHORIZE_URL, ACCESS_TOKEN_URL); OAuthConsumer consumer = new OAuthConsumer( CALL_BACK_URL, CONSUMER_KEY, COMSUMER_SECRET, provider); OAuthAccessor accessor = new OAuthAccessor( consumer);アプリケーション登録で手に入れたパラメータを使って各クラスを初期化しています。
//get request token first from Twitter.com HashMapリクエストトークンは認証をリクエストする際に必要になります。params = new HashMap (); params.put( "oauth_callback", OAuth.addParameters(accessor.consumer.callbackURL, "dest", destUrl)); //get request token first from Twitter.com client.getRequestToken(accessor, null, params.entrySet());
パラメータ"oauth_callback"は、リダイレクトによるコールバックが呼び出すURLを指定します。アプリケーション登録でコールバックURLは登録しているのですが、ここではそのURLにパラメータ"dest"を追加するために使っています。"oauth_callback"を指定しない場合、アプリケーション登録で登録したURLにリダイレクトされます。
次のパートでは、getRequestTokenでTwitter(プロバイダ)から取得されたリクエストトークンを取り出し、認証先URLを生成しています。
うまくいけば、ユーザーは以下のようなTwitterのページに誘導されます。Twitterと連携するWebアプリケーションを使っている方なら、見たことがあるのではないでしょうか。
//build redirect path to twitter authentication page redirectTo = OAuth.addParameters( accessor.consumer.serviceProvider.userAuthorizationURL,//auth URL(twitter.com) "oauth_token", accessor.requestToken// );
ユーザーが「許可する」もしくは「拒否する」をクリックすると、"oauth_callback"で指定したURLにリダイレクトされます。
次にコールバック先での処理を見てみましょう。
コールバックでの処理
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
String destUrl = request.getParameter( "dest");
String requestToken = request.getParameter(OAuth.OAUTH_TOKEN);
if( requestToken == null){
//rejected by USER
response.sendRedirect( destUrl);
return;
}
String verifire = request.getParameter(OAuth.OAUTH_VERIFIER);
if( verifire == null){
//rejected by USER
response.sendRedirect( destUrl);
return;
}
OAuthClient client = new OAuthClient( new URLConnectionClient());
OAuthServiceProvider provider = new OAuthServiceProvider( REQUEST_TOKEN_URL, AUTHORIZE_URL, ACCESS_TOKEN_URL);
OAuthConsumer consumer = new OAuthConsumer( CALL_BACK_URL,
CONSUMER_KEY, COMSUMER_SECRET, provider);
OAuthAccessor accessor = new OAuthAccessor( consumer);
accessor.requestToken = requestToken;
try {
HashMap params = new HashMap();
params.put( OAuth.OAUTH_VERIFIER, verifire);
//get access token and secret from twitter.com
client.getAccessToken(accessor, null, params.entrySet());
} catch (OAuthException e) {
throw new RuntimeException( "It failed to authenticate Twitter account", e);
} catch (URISyntaxException e) {
throw new RuntimeException( "It failed to authenticate Twitter account", e);
}
//Retrieve user's information
OAuthMessage oMessage = null;
try {
oMessage = accessor.newRequestMessage("GET", "http://api.twitter.com/1/account/verify_credentials.json", null);
} catch (OAuthException e) {
throw new RuntimeException( "It failed to authenticate Twitter account", e);
} catch (URISyntaxException e) {
throw new RuntimeException( "It failed to authenticate Twitter account", e);
}
OAuthResponseMessage rMessage = client.access(oMessage, ParameterStyle.AUTHORIZATION_HEADER);
int status = rMessage.getHttpResponse().getStatusCode();
if( status == HttpResponseMessage.STATUS_OK){
String jsonStr = rMessage.readBodyAsString();
JSONObject jObj = JSONObject.fromObject(jsonStr);
String userId = jObj.optString("id");
accessor.setProperty( "id", userId);
}else{
throw new RuntimeException( "It failed to authenticate Twitter account STATUS CODE:"+status);
}
//Store access token and secret to Cookie
TwitterAPIUtil.storeTokenToCookie(accessor, response);
response.sendRedirect( destUrl);
}
まず、認証によりTwitter(プロバイダ)から付け加えられたパラメータを取得しています。
String requestToken = request.getParameter(OAuth.OAUTH_TOKEN);
if( requestToken == null){
//rejected by USER
response.sendRedirect( destUrl);
return;
}
String verifire = request.getParameter(OAuth.OAUTH_VERIFIER);
if( verifire == null){
//rejected by USER
response.sendRedirect( destUrl);
return;
}
これらがセットされていなかった場合、ユーザーが認証を拒否したと考えられますので、あらかじめコールバックURLに追加したパラメータに積んでおいた飛び先URLにリダイレクトさせます。先の処理と同様にOAuthのクラスを初期化後、認証により手に入ったリクエストトークン、ベリファイアを使ってTwitter(プロバイダ)からアクセストークンとトークンシークレットを取得します。
別の場所で認証情報を使ってAPIを呼び出すには、以下のようにします。
accessor.requestToken = requestToken;
try {
HashMap params = new HashMap();
params.put( OAuth.OAUTH_VERIFIER, verifire);
//get access token and secret from twitter.com
client.getAccessToken(accessor, null, params.entrySet());
} catch (OAuthException e) {
throw new RuntimeException( "It failed to authenticate Twitter account", e);
} catch (URISyntaxException e) {
throw new RuntimeException( "It failed to authenticate Twitter account", e);
}
アクセストークンとトークンシークレットはAPIをコールするために必要になります。
うまく取得できた場合、すでにOAuthAccessorオブジェクトにアクセストークンとトークンシークレットは格納されていますので、Twitter APIを呼び出すことができます。
//Retrieve user's information
OAuthMessage oMessage = null;
try {
oMessage = accessor.newRequestMessage("GET", "http://api.twitter.com/1/account/verify_credentials.json", null);
} catch (OAuthException e) {
throw new RuntimeException( "It failed to authenticate Twitter account", e);
} catch (URISyntaxException e) {
throw new RuntimeException( "It failed to authenticate Twitter account", e);
}
OAuthResponseMessage rMessage = client.access(oMessage, ParameterStyle.AUTHORIZATION_HEADER);
int status = rMessage.getHttpResponse().getStatusCode();
if( status == HttpResponseMessage.STATUS_OK){
String jsonStr = rMessage.readBodyAsString();
JSONObject jObj = JSONObject.fromObject(jsonStr);
String userId = jObj.optString("id");
accessor.setProperty( "id", userId);
}else{
throw new RuntimeException( "It failed to authenticate Twitter account STATUS CODE:"+status);
}
ここでは、認証済みユーザー自身のユーザー情報を返す account/verify_credentials APIを呼び出しています。レスポンスのJSONを扱うのには、Json-lib ライブラリを使用しています。最後に認証情報をクッキーに保存しています。
//Store access token and secret to Cookie TwitterAPIUtil.storeTokenToCookie(accessor, response);このメソッドの中身は以下のようになっています。
public static void storeTokenToCookie( OAuthAccessor accessor, HttpServletResponse response, int maxAge){
Cookie cookie = new Cookie( accessor.consumer.consumerKey+"_requesttoken", accessor.requestToken);
cookie.setPath( "/" );
cookie.setMaxAge( maxAge);
response.addCookie( cookie);
cookie = new Cookie( accessor.consumer.consumerKey+"_accesstoken", accessor.accessToken);
cookie.setPath( "/" );
cookie.setMaxAge( maxAge);
response.addCookie( cookie);
cookie = new Cookie( accessor.consumer.consumerKey+"_secret", accessor.tokenSecret);
cookie.setPath( "/" );
cookie.setMaxAge( maxAge);
response.addCookie( cookie);
String userId = (String)accessor.getProperty("id");
if( userId == null){
userId = "";
}
cookie = new Cookie( accessor.consumer.consumerKey+"_id", userId);
cookie.setPath( "/" );
cookie.setMaxAge( maxAge);
response.addCookie( cookie);
}
認証情報は、もちろんデータベースなどのストレージに保存することも可能です。しかし、これらの認証情報は、Twitter(プロバイダ)のサイトでユーザーが一方的に無効にできることには留意しておく必要があるでしょう。
.........
OAuthClient client = new OAuthClient( new URLConnectionClient());
OAuthServiceProvider provider = new OAuthServiceProvider( REQUEST_TOKEN_URL, AUTHORIZE_URL, ACCESS_TOKEN_URL);
OAuthConsumer consumer = new OAuthConsumer( CALL_BACK_URL,
CONSUMER_KEY, COMSUMER_SECRET, provider);
OAuthAccessor accessor = new OAuthAccessor( consumer);
//try to retrieve token
Cookie[] cookies = request.getCookies();
for( int i = 0; i < cookies.length; ++i){
Cookie cookie = cookies[i];
if( cookie.getName().equals( accessor.consumer.consumerKey+"_accesstoken")){
accessor.accessToken = cookie.getValue();
}else if( cookie.getName().equals( accessor.consumer.consumerKey+"_requesttoken")){
accessor.requestToken = cookie.getValue();
}else if( cookie.getName().equals( accessor.consumer.consumerKey+"_secret")){
accessor.tokenSecret = cookie.getValue();
}else if( cookie.getName().equals( accessor.consumer.consumerKey+"_id")){
accessor.setProperty( "id", cookie.getValue());
}
}
.........
非常に駆け足になってしまいましたが、どのような印象を受けましたか?ライブラリを使用することで、かなりOAuthへの敷居が下がったと感じた方もおられるのではないでしょうか?この記事がちょっとでもだれかのお役に立てば幸いです。