ばかおもちゃ本店:Youtube twitter:@sashimizakana Amazon.co.jpアソシエイト

2013年11月8日金曜日

AWSのDynamoDBをJavaScriptから直接操作する

前回の記事のとおり、AWSのSDKがブラウザ用JavaScriptで利用できるようになった。
今回は実際に試して利用できるようになったまでの手順を記す。
ちなみに検索しても日本語の情報はおろか英語の情報すらあまり見つからず、総当り的に試行錯誤ており、またDynamoDBそのものとかGoogleの認証周りは今ひとつわかっていないので、手順には無駄や間違った箇所があるかもしれない。そのあたりは各自で修正していただきたい。(そして出来れば私にも教えてほしい)

今回やることは、Googleで認証を受けて、その認証トークンを使ってDynamoDBに接続して、データを取得・設定する。利用の想定はGoogleのユーザーIDごとにjsonかなにかの文字列を保存しておく単純なアプリケーションとする。

クライアント側での大まかな流れとしては、Googleでログインしてもらってそこから返されたid_tokenからGoogleのuser_idを取得する。AWSのDynamoDBのキー値はこのuser_idにする。DynamoDBとのアクセス時に一緒にid_tokenを渡すことでAWS側でもuser_idが正しいことが照合され、自分のuser_idがキーのデータのみにアクセスできるようになる。

利用までの手順としては以下のような流れになる。
  1. Googleにアプリケーション登録をする
  2. AWSでDynamoDBにテーブルを作成する
  3. DynamoDBでWeb認証のときのポリシーを作成できるので作ってコピーしておく
  4. AWSでIAMにDynamoDBにアクセスできるRoleを作成する(3のポリシーを使う)
  5. ブラウザ上でDynamoDBに接続するアプリケーションを作成する
ではひとつずつ説明していく。

1.Googleにアプリケーション登録をする

Google APIs Console
Google Cloud Console

上記リンクのどっちでも良い(下の方が新しい)。
これらは本来GoogleのAPI(地図とかカレンダーとか)を利用申請する画面なのだけど、今回はログインを利用するだけなので登録のみでOK。適当に登録するとAPI Accessとかの欄にClient IDがあるのでそれを記録しておく。****.apps.googleusercontent.comみたいなやつ。****.projectというやつは違うので気を付ける。Client Secretは使わない。
ちなみに下の方はGoogle App Engineなんかのプロジェクトとも統合されていて、その設定を行える。
このあたりはたぶんググればどんだけでも情報があるので省略。

2.AWSでDynamoDBにテーブルを作成する

ふつうにCreate Tabelで作成する。
ググれば(略)
スループットを全Table合計でwrite:5/read:10以上に設定すると、時間単価が有料になるので気を付ける(初めて作るならどっちも1にしておけば大丈夫)。

3.DynamoDBでWeb認証のときのポリシーを作成できるので作ってコピーしておく

これ。


Identity ProviderをGoogleにして、権限をあたえる処理にチェックをする。
Allowed Attributesってので列制限もできるみたい。


ここで設定するわけではなくPolicy Documentの欄の設定値をコピーしておく。
ざっくり設定に触れると、dynamodb:LeadingKeysが${accounts.google.com:sub}になっているので、キー値がgoogleのsub(ユーザーID)と一致したときのみ権限が与えられるようになっている。
id_tokenはgoogleのエンドポイントに投げるとユーザーID、アプリケーションID等々が返されるので、正しいアプリケーションであるかどうかとか、正しいユーザーIDであるかどうかが分かるのでAWS側で照合してくれてるっぽい。

4.AWSでIAMにDynamoDBにアクセスできるRoleを作成する(3のポリシーを使う)

IAMのサービスにアクセスしてRoleを作成する。



 適当なRole名をつける。

一番下のを選ぶ。

Googleを選択してAudienceに Client IDを設定する。
これであとで渡すtokenが他のアプリに発行されたものじゃないことが確認される。

確認して次へ。

さっきのポリシーを入れるためにCustom Policyを選択する。

3で作成したポリシーをコピペする。

完了。
あとで使うのでこのRoleのSummaryからRole ARNをメモっておく。
こんな感じのやつ。
arn:aws:iam::(数字):role/(Role名)

5.ブラウザ上でDynamoDBに接続するアプリケーションを作成する

Googleから取得する必要があるのは、id_tokenというJWTっていうJSONの情報をまとめて署名してるナニ(よく知らない)と、Googleのuser_idの二つ。
id_tokenはGoogleのエンドポイントに渡すとそのトークンがどのアプリケーションIDに対して発行されたものかとか、どのuser_idに対するものであるとか、有効期限はいつまでだとか分かるのでその辺で有効な認証情報かAWS側で照合するのに利用されてるっぽい。
user_idはDynamoDBに格納するときのキーとして使うことになる。
これまでの手順が間違いなく出来ていたなら、DynamoDBにはこのid_tokenで確認されたuser_id以外のキーを受け入れないようになっている。

id_tokenとuser_idを取得する方法

これには調べた限りふたつの方法があって、楽なんだけど今ひとつ微妙なGoogle+ボタンを使う方と、面倒だけどちゃんと制御できるGoogle APIのJavaScriptライブラリを使う方法がある。多分内部的には同じ手順を踏んでるはず。
どちらの場合も上の手順で取得しておいたClient IDと、ユーザーにどこまでの権限を要求するかということを決めるscopeというものを用意する必要がある。ここではユーザーの基本的な情報(ユーザーIDの番号とアカウント)だけを要求する最低限のものにする。
その場合以下のようなアドレスをscopeに設定する。

https://www.googleapis.com/auth/userinfo.profile

各scopeの要求時に表示される画面なんかは以下のアドレスで確認できる。

OAuth 2.0 Playground

ちなみに上記のサイトはGoogle認証のOAuth2.0の手順を追っかけられるサイトなので、何が起きてるのかとか全く分からないなら色々いじくり回すと良いと思う。

で、話を戻して認証にはGoogle+ボタンを使う場合と直接やる場合とある。ただ調べ方が悪いのか、もともとそういうもんなのか、+ボタンを使う場合、上記のscopeが常にGooge+ API v1のloginを要求する権限が要求されてしまう。Google+を利用したことがないので分からないが、たぶんtwitterで言うと[今何々をプレイしてます!]みたいなものを自動でつぶやかせるために使うような権限だろうと思う。facebookで言うとウォールに載るかどうかみたいなもんだろう(facebookも使ったことがないのでこのたとえが正しいのかも知らない)scopeで言うと、https://www.googleapis.com/auth/plus.loginのやつがそれだ。
+ボタンって言ってるんだし、たぶんもともとそういうもんなんだろう。
上のuserinfo.profileで要求すると、+内でのユーザー情報と、+のサークルに知らせる権限と、Googleの情報とかなるので要求が多くて敬遠されそうな感じがする。あとログイン済みの場合、勝手に画面にトーストを表示させるを消す方法も分からなくて嫌だったので使わなかった。

ただ、Google+のユーザーでその方面で共有して欲しいと思うなら、使い方は簡単なのでおすすめだ。GoogleがホストしてるJavaScriptのファイルをロードして、spanに決まったクラスやdata属性でアプリケーション情報なんかを設定するだけでid_tokenを含んだデータがcallbackに帰ってくる。

以下に日本語で公式ドキュメントがあるので読めばすぐに使える。

Google Platform — Google Developers

user_idに関しては、手順が共通なのでライブラリを直接操作してid_tokenを取得する方法のあとに書く。

Google+ボタンを使わない方法

というわけでライブラリの使い方。ぱちぱち。
ちなみに+ボタンを使わないときでも、Google+ボタンガイドラインみたいなものがあって、それに準拠しなきゃならないのか悩むところだけど、何度か読み返してもこれはGoogle+の連携アプリとして作成するときはっつう話っぽいので多分大丈夫だと思う。+の権限を使うなら遵守すべき。
で本題。

Google JavaScript API - Google Platform — Google Developers
JavaScript Client Library Reference - Google APIs Client Library for JavaScript (Beta) — Google Developers

このあたりのリファレンス読めば分かる(おわり)。

なのだけど、一応ひっかかるポイントもあるのでサンプル。
gapi.auth.authorize({
    client_id:"******.apps.googleusercontent.com"
    ,immediate:true 
    ,response_type:"token id_token" 
    ,scope:"https://www.googleapis.com/auth/userinfo.profile"
},function(response){
    if(response && response.access_token){
        //ログインできたときの処理
    }else{
        //ログインできなかったときの処理
    }
});
上記の引数で一番重要なのはresponse_typeに"token id_token"という風にid_tokenとtokenのどっちも指定すること。tokenはあとでuser_idを問い合わせるときに使う(たぶんid_tokenで問い合わせることも出来ると思うんだけど調べてない)。ちなみに指定しないとtokenになるみたいでid_tokenが取得できず悩んだ。

immediateは連携許可の画面を出すか否かというフラグで、falseの場合、連携の有無に関係なく連携許可の画面がポップアップする。ただ既に連携の許可があった場合、ポップアップはちらりと見えてすぐ消える。そして上記のログインできたときの処理が流れる。許可が無ければ、あるいはログインしていなければその画面内でログイン|承認を受けられればログインできたときの処理が流れる。

trueの場合、連携の有無に関係なく、ポップアップは表示されない。連携がありログインしていれば問答無用でログイン処理に移行し、そうでなければできなかったときの処理が行われる。一長一短である。

つまりログインの処理をまっとうにやろうとすると、gapi.auth.authorizeはimmediateフラグをtrueで流したのち、ログインできなかったらfalseで流し直すか、ログインボタンを表示して押されたらfalseで実行するような形にすべきなのだと思う。
そうすると認証済みユーザーはシームレスに、新規ユーザーは承認画面に移行できる。

user_idを取得する

で、上記のどちらかの方法でid_tokenとaccess_tokenを取得できたはずだ。
そもそもこのtokenって何のtokenかというと連携許可を受けたデータにアクセスするためのtokenなので、このtokenを自分が許可を受けた権限で要求可能なエンドポイントに投げてやれば必要な情報が手に入る(必要な操作を行える)という寸法である。

これも上記のリファレンスに載っているのだけど、サンプルはこんな感じ。
var request = gapi.client.request({
    'path':'/oauth2/v2/userinfo'
    ,'params': {key:/*取得したaccess_token*/}
});

request.execute(function(userinfo){
    /*user_idを利用した処理*/
});
#callbackばっかりでネストが深くなりそうならdeferを使おう(jQueryだとDeferred)

やっとAWS SDK for JavaScriptをさわる

AWS SDK for JavaScript in the Browser

ざっと読めばだいたい大丈夫。
まずhttps://sdk.amazonaws.com/js/aws-sdk-2.0.0-rc1.min.jsがsdkなのでscriptタグでロードしてやる。
そいでサンプルはこんな感じ。
AWS.config.credentials = new AWS.WebIdentityCredentials({
    RoleArn: "arn:aws:iam::*******:role/TestRole"
    ,WebIdentityToken: id_token
});
AWS.config.region = "ap-northeast-1";
var DB = new AWS.DynamoDB();

//値を入れる
DB.putItem({
    TableName:"TestTable"
    ,Item:{user_id:{S:user_id},data:{S:"TEST"}}
},function(response){
    //エラーがあればresponseに入る
});

//値を取り出す
DB.getItem({
    TableName:"TestTable"
    ,Key:{user_id:{S:user_id}}
},function(response,data){
    //データに取り出した値が入る
});
AWS.configに設定しておくとS3とかDynamoDBとかで共通のリージョンとか認証情報が使える感じ。RoleArnにRoleを作ったときにメモっておいたArnを入れて、id_tokenを渡して、regionを設定すればあとはテーブルを操作するだけ。

ItemとかKeyでDynamoDB側に値を渡しているときのSとかいう名前のプロパティはその値の型名でSはString、NはNumberとかそんな感じ。
やったーEC2なしでDynamoDBにアクセスできた!