火曜日, 3月 09, 2010

【Google App Engine】 WSSEチケット認証でGoogleDocsとFederationさせる このエントリーを含むはてなブックマーク


GoogleDocsとGAEの連携
 GoogleDocsやGoogleAppsとGAEは相性がよく、これらを組み合わせたアプリを作りたいと考えている方も多いだろう。私もこの組み合わせは気に入っていて、下図のような、Google Apps 3層アーキテクチャーを基本にしたマッシュアップアプリが作れないか日々、研究している。
 この記事を書いてるあいだに、タイムリーにグーグル、Google Apps向けマーケットを開設なんて発表があった。今、Google Apps連携が熱い。



 Google Docsについては、先日、すごいのはGDriveより全文検索でしょ!?にも書いたが、PDFやEXCELといった様々なタイプのコンテンツをほぼ無制限に格納でき、かつ全文検索もできるので大変便利である。Google Docs List APIのAnyType Uploadなどは、Google AppsのPremier Edition向けとされているが、普通のGoogleアカウントでもPDFであればUploadができて、テキストファイルもcreateできるので、Premier Editionでなくても実質的にAnyTypeを扱えている。(保証はできないが・・)

 このように、Google AppsとGAEを連携させるのは非常に有効なのだが若干問題もある。一つには、Google認証だけでは、サイトを跨ぐ認証機能が不十分でうまくアプリケーションを連携できないこと。二つ目は、GAEには30秒ルールやURL Fetchの最大1MBという制約があり、GAEを介してしまうとGoogle Docsの大きなサイズのコンテンツを扱えないこと。

 これを解決するには、OAuthのような、サイトを跨ったアクセス権(認可)委譲の仕組みが必要となるが、OAuthを使ったとしても第3者の所有するリソースにはアクセスできない。
 そこで、下図のようなWSSEチケットを使った仕組みで解決してみることを考えた。これは、Federationと呼んでいるが、WSSEチケット認証機能をサービスプロバイダで受け持ち、Google Docsにユーザのアクセス権を付与することで第三者によるコンテンツ取得を可能にするものである。狭い意味でシングルサインオンのことであるが、第三者への認可を行えるという意味では別物と考えてもらいたい。
 WSSEは、WS-Securityと実質的に同じものであり、古くから存在するがあまり普及していない。Atom Pubで本命視されながら結局採用されなかった。


<やりたいこと>
  • ユーザYはサービスプロバイダZのコンテンツを取得したい。
  • YはコンシューマXのアカウントである。ZはYのことを知らないし知ってはいけない。
  • ZにとってYからのリクエストはXコンシューマからのリクエストとして処理しなければならない。

  • 一方で、コンテンツ取得の際はXを経由せずに行いたい。(1MB制限回避のため)

  • ZのコンテンツはXに対して常に開放されているわけではなく、普段は閉じられていてリクエストがあってはじめて提供できるようにしたい。
  • また課金などに使うためアクセスログも取りたい。



<処理の流れ>

  1. ユーザ(Yアカウント)はXアカウントが所有するGoogle Appsの一員である。

  2. ユーザ(Yアカウント)がZアカウントのGoogle Docsにアクセスしたい場合、サービスプロバイダにリクエストすることで、Xアカウントのドメイン名がGoogle DocsのACLに追加されてREAD権限がつく。

  3. ダウンロード後(数分後)サービスプロバイダが登録したTaskQueueによりXドメインのREAD権限が削除される。



<制約など>
  • Google DocsのACLには、Googleアカウントかドメイン名のどちらかを指定する。
  • すべての人への公開はGoogle Docs List API(Premier Editionを除く)からはできない。
  • 今回のケースでは、ZはYのことを知ってはいけないので、Xのドメイン名を追加する方法をとった。





 ① ユーザのアクションによりブラウザから以下のGETメッセージがコンシューマに飛ぶ。すると、コンシューマはWSSEリダイレクトURLを生成してブラウザに返す。

http://consumer.com/getcontent?key=2004000001.pdf


 ② WSSEチケットは、実際には以下のようなリダイレクトメッセージ(HTTP 302)であり、これを受けたブラウザは自動的にプロバイダに遷移する。nonceはランダムな値、createdは作成日時、userはXユーザである。

HTTP/1.1 302 Moved Temporarily
Location: http://provider.com/getcontent?key=2004000001.pdf&user=xxx&nonce=nnn&created=ccc&digest=ddd


 ③ WSSE URLを受けたサービスプロバイダは、保持しているXアカウントのpaswordとnonceとcreatedを結合してSHA1でハッシュ値を計算し、digestと比較する。一致すれば認証成功、一致しないか、既に使用済みのdigestであれば認証失敗としてブラウザに結果を返す。(digestはDatastoreに記録するなどして使用済かどうかを判定する)
   (パスワードを保持しているのはコンシューマXのサーバ内であり、ユーザYにはワンタイムだけ有効なハッシュ値だけが渡されるのがミソ。また、チケットを発行するには、必ずXサーバにログインしなければならないので、この時点で認可されていると考えてよい。)

 ④ 認証成功の場合、以下のような実際のダウンロードURLを返す。(これもリダイレクト)

HTTP/1.1 302 Moved Temporarily
Location: https://docs.google.com/uc?id=xxxxxxxxxxxxxxxxxxxxxxxxxxx&export=download


はこのとき、GAEだけではなく、Google Docsにもログインしていなければ、ダウンロード失敗となる。これの回避方法は後ほど述べる。また、document idが一瞬見えてしまうのが気になるところだが、コピーできるわけでもないので、まあこれはしょうがないと考えるしかないだろう。(わかったところで通常はアクセス権がないので大丈夫)

WSSE Sample




【コラム】FederationとGlobalの違い

 Globalは、アプリ内の連携という意味で使われることが多い。GAEでGlobalにアクセスできるものといえば、DatastoreやMemcacheなどがある。同じアプリであっても、内部変数などのLocalなリソースは1インスタンス内でしか利用できないが、Globalなリソースでは複数のインスタンス間で共有できる。(ちなみに、GAEでは異なるバージョンを10個持てるが、これらは1つのアプリとしてみなされるため、Globalなリソースを各々から参照できる。)
 ところが、アプリ(サイト)が異なれば、Globalなリソースであってもアクセスすることができない。Federationには「連携」という意味があり、サイトを跨ぐ連携という意味でよく用いられる。今回の話は、GAEのアプリとGoogle Docsとの連携なので、サイトを跨ぐFederationの仕組みが必要になる。


Googleのログイン認証について


 ちょっと本題からずれるが、Googleのログイン認証についていろいろ調査したことをまとめてみる。(話が複雑になるのでOAuthやAuthSubの話は省略する。)
 
ログイン画面からログインする

 ユーザアプリが勝手に画面をカストマイズすることは(たぶん)できない。以下のように、Bloggerなどは、ServiceLoginBoxを呼び出すことで独自のログイン画面を表示しているが、GAEの場合はServiceLoginが呼び出されている。


Blogger:
https://www.google.com/accounts/ServiceLoginBox?service=blogger&continue=https://www.blogger.com/loginz%3Fd%3Dhttps%253A%252F%252Fwww.blogger.com%252Fstart%253Fhl%253Dja%26a%3DALL&passive=true&alinsu=1&aplinsu=1&alwf=true&skipvpage=true&rm=false&showra=1&fpui=2&naui=8




GAE:
https://www.google.com/a/virtual-tech.net/ServiceLogin?service=ah&passive=true&continue=http://XXXXXX/_ah/login%3Fcontinue%3Dhttp://XXXXXX&ltmpl=ga&ahname=YYYYY&sig=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx



 また、Googleトークのようにユーザ認証なしに1クリックでGMAILボックスが開くような操作はブラウザからはできない。その理由は、ログイン画面には、GALXというソルトが埋め込まれており、表示されるたびに毎回生成されるからである。ソルト(塩)とは、ダイジェストを生成するときに含める乱数のことで、認証が成功して認証キーが生成されるまでセッションに含まれている。GALXはログイン画面を表示させないと取得できないので自動化は無理というもの。ちなみにGoogleトークではGALXを独自に生成しているっぽいが、これはGoogleだから成せる技である。

 認証キーの取得では、ログイン画面から以外に、ClientLoginサービスを呼び出す方法がある。ClientLogin for Installed Applicationsにあるように、USERIDなどをPOSTすることで、SID,LSID,Authといった認証キーを取得できるが、ブラウザから直接取得することはできない。ClientLoginサービスをサーブレットにして、認証キーを取得できたとしても、ブラウザからHTTPヘッダを付けることができないので、Authorization: GoogleLogin auth=your-authentication-tokenをつけてGETすることができない。SID,LSIDをCookieにつけようと思ってもクロスドメインにひっかかるのでできない。(ブラウザからgoogle.comにアクセスしてセットしなければならない。)XHR(AJAX)を使ってやるのも同様の理由でできない。要するにClientLogin認証はブラウザからは使えないということ。

 ブラウザ以外であれば、いろいろやり方はあるようである。


The Chromium Projects

For our needs, the normal Google Accounts ClientLogin API is not enough, as it is designed to provide cookies that allow a client application to authenticate to a single Google service. We want the cookies your browser gets when you run through a normal web-based login, so that we can get them into Chromium after the user's session has begun. Therefore, we currently go through a three-step process:

1. https://www.google.com/accounts/ClientLogin, to get a Google cookie
2. https://www.google.com/accounts/IssueAuthToken, to get a one-time use token (good for a couple of minutes) that will authenticate the user to any service
3. https://www.google.com/accounts/TokenAuth, to exchange the token for the full set of browser cookies we need to do SSO


Picasaの場合


When I run Picasa and tried to log in to Picasa Web, it connected to XSP and I was finally able to see how they were authenticating. The two interesting URLs were:

* https://www.google.com/accounts/ClientAuth
Posting the user name and password here gives us back a LSID and a SID value.
* https://www.google.com/accounts/IssueAuthToken
Posting the SID, LSID and service name (lh2 for Picasa Web) gives us an AuthToken.

Now that we've got the AuthToken, we just need to append it to the query string as auth=AuthToken when getting Picasa Web API information from http://picasaweb.google.com/api/urls?version=1. If you get that URL without the AuthToken, you will only get the read-only stuff, but adding the AuthToken gives you the post value, which is the URL used when sending commands to the server and also a cookie that should be used on the rest of the session to prove that you're authorized.

The authentication process now requires 2 POSTs and 1 GET, while before it was trying to emulate a web browser and got a bunch of redirections and lots of cookies being set and unset. Oh, and now google-sharp works on windows with the MS runtime too!


 このPython Sample(セキュリティ例外を認めて強制表示させてください)のなかでは、X-GOOGLE-TOKENを取得するというところで、上記と同じような処理を実装している。

GAEでログイン後にGoogle Docsにログインする

 一度、GAEアプリにログインできれば、Google Docsにログインすることは難しくない。ただ、GAEアプリ(service=ah)のSIDしか取得できていない状態なので、Google Docs(service=writely)のSIDを取得する必要がある。それは、以下のstatコマンドをGETリクエストしてあげればよい。(URL中のvirtual-tech.netはGoogle Appsのドメイン名)


https://www.google.com/a/virtual-tech.net/ServiceLogin?service=writely&passive=true&nui=1&continue=https%3A%2F%2Fdocs.google.com%3A443%2Fa%2Fvirtual-tech.net%2Fstat


 画面表示の際についでにこのGETリクエストも発行されるようにしておけば、再度ログインを要求されることなく静かにGoogle Docsにアクセスできる。(シングルサインオン)例えば、以下のGETリクエストを実行すると実際にコンテンツをダウンロードできる。(idはドキュメントID。Google Docsの画面で共有にしてダウンロードすると確認できる)


https://docs.google.com/uc?id=xxxxxxxxxxxxxxxxxxxxxxxxxxx&export=download



 ちなみに、downloadに続けて.htmlとすると、ブラウザはhtmlとして判断するので表示できてしまう。JavaScriptも同様に動かすことができる。ただ、提供元が、docs.google.comからdoc-xx-xx-docs.googleusercontent.comに遷移しているので、setCookieなどを仕込んで悪さをしようと思っても無駄である。


https://docs.google.com/uc?id=xxxxxxxxxxxxxxxxxxxxxxxxxxx&export=download.html



以下は、ServiceLoginを実行してからダウンロードを実行する方法。遅いのであまりおすすめではない。


https://www.google.com/a/virtual-tech.net/ServiceLogin?service=writely&passive=true&nui=1&continue=https%3A%2F%2Fdocs.google.com%2Fa%2Fvirtual-tech.net%2Fuc%3Fexport%3Ddownload%26id%3Dxxxxxxxxxxxxx



 ダウンロードはGAEを経由せずにできるようになったが、アップロードはブラウザからは無理のようである。何とかしたいけどよい方法はないだろうか。

今回の解析に使ったツール:

Live HTTP Header
FireBug+FireCookie

shin1ogawaさんからコメントをいただいたので追記:

イマイチ目的がわからない。gmail.comドメインのAppengineアプリから、userdomain.comのDocsのリソースにアクセスしたいなら、OAuthで普通に可能だけど、それとはまた違うのかな。 - shin1ogawa SoraUsagi 経由
うーん、ひょっとしてAppsの2LeggedOAuthでやれば良い話? - shin1ogawa SoraUsagi 経由


私もOAuthでやれるんじゃないかと思っていろいろ調べてみたのですがだめでした。 ><;。OAuthと周辺技術の勉強会を見ると、GFCの仕組みを使えばできそうな雰囲気なのですが・・・。もしわかったら教えてください。

<追記>
 ああ、shin1ogawaさんがいっていたのは、コンシューマとサービスプロバイダ間を2LeggedOAuthで繋ぐって話か。(こんなのあったのね。知らんかった)2LeggedOAuthはHMAC-SHA1を使った共通鍵認証みたいなので鍵の保持が必要だけど、”2LeggedOAuth”ってすごそうな名前なので、古臭いWSSEよりはいいかもしれない。
 ついでに調べておきます。

0 件のコメント:

 
© 2006-2015 Virtual Technology
当サイトではGoogle Analyticsを使ってウェブサイトのトラフィック情報を収集しています。詳しくは、プライバシーポリシーを参照してください。