概要
appengine/java で提供しているサービス向けのガジェットを用意するにあたり署名付きリクエストを使ってユーザー(viwer)を特定することをしたかったのだが、その署名を検証する方法がなかなか見つからずに苦労した。
ようやく有効な方法を発見できたのでここにまとめてみたい。
署名付きリクエスト
gadgets.io.makeRequest()で認証タイプをSIGNEDに指定するとOpenSocialコンテナ側(GoogleSites)がapp_id,app_url,owner_id,viewer_idを付加してサーバーにプロキシーしてくれる。
しかし、サーバー側ではそれが本当にガジェット(コンテナ)から送信されたものなのかどうか検証できなければならない。
そのしくみとしてOAuth Signatureを使った署名検証の方式が用意されている。(opensocialの仕様)
署名付きリクエストの検証方法には、
- コンテナの証明書を使う方法(RSA-SHA1方式)
- あらかじめConsumerKey,ConsumerSecretをコンテナに登録しておく方法(HMAC-SHA1方式)
の2通りがあるようだった。
mixiなどは前者の方式が使われているようでこちらの方式のほうが一般的(?)なのかもしれないが、
googleの場合はこの方式で使う証明書の有効期限が2009年で切れており、他に有効なドキュメントも見つからず完全に放置プレイっぽい匂いを感じていたので別の方法を探した。
ちなみにこの方式について書かれている mixiのデベロッパーガイドは 今回の実装にあたりかなり参考になった。
そこでもうひとつのHMAC-SHA1方式で実装する方法を以下にまとめてみる。
コンテナに登録
Googleの場合は以下のページでガジェットのURLを登録し、発行されたConsumerKey,ConsumerSecretを使用して検証を行う。
リクエストを検証するコードサンプル(java)
ここからjava用のコードをとってきて使用する。(net.oauth.*)
OAuthServiceProvider provider = new OAuthServiceProvider(null, null, null); OAuthConsumer consumer = new OAuthConsumer(null, CONSUMER_KEY, CONSUMER_SECRET, provider); OAuthValidator validator = new SimpleOAuthValidator(); OAuthAccessor accessor = new OAuthAccessor(consumer); accessor.tokenSecret = ""; // nullだとまずいかもしれないので・・
まず、OAuth関連のクラスをnewする。今回は署名検証するだけなので不要なパラメータはnullでOK。
OAuthMessage message = new OAuthMessage( request.getMethod(), parseRequestUrl(request).toString(), parseRequestParameters(request));
requestを仕様に従って正規化して、OAuthMessageのコンストラクタに渡す。
ここで使っている、parseRequestUrl, parseRequestParametersは以下のような実装となる。
private List<OAuth.Parameter> parseRequestParameters(HttpServletRequest request) { List<OAuth.Parameter> parameters = new ArrayList<OAuth.Parameter>(); for (Object e : request.getParameterMap().entrySet()) { @SuppressWarnings("unchecked") Map.Entry<String, String> entry = (Map.Entry<String, String>) e; for (String value : entry.getValue()) { parameters.add(new OAuth.Parameter(entry.getKey(), value)); } } return parameters; } private StringBuilder parseRequestUrl(HttpServletRequest request) { StringBuilder url = new StringBuilder(); String scheme = request.getScheme(); url.append(scheme); url.append("://"); url.append(request.getServerName()); int port = request.getLocalPort(); if (port == 0) { //nothing } else if ( (scheme.equals("http") && port != 80)||(scheme.equals("https") && port != 443) ) { url.append(":"); url.append(port); } url.append(request.getRequestURI()); return url; }
最後にvalidateMessageを実行する。検証エラーとなった場合はExceptionがThrowされる。
validator.validateMessage(message, accessor);
これで署名検証に関する基本的な実装はOKなはずだ。
その他の検証項目
[1]
上記SimpleOAuthValidatorの実装ではinstanceが永続化されている間のみnonceのチェックが働く仕様となっているので
appengineなど分散環境では正しくチェックはできない。
nonceのチェックは別途実装するかValidatorの仕様を修正する必要がありそうだ。
[2]
本検証によりリクエストが正しくコンテナからプロキシーされてきたものだということは保証されるが、
「自分のアプリから来たリクエストか」どうかは検証できていない。
それが必要な場合は送られてきたapp_idを検証する処理が必要となる。