AWS SDK for JavaScriptでAmazon S3とDynamoDBをクライアントサイドJavaScriptのみで操作する

久保田です。

AWS SDK for JavaScriptのデベロッパープレビューがリリースされました。AWSは今までウェブサービスのサーバサイドからしか扱えませんでしたが、このライブラリを用いることで、AWSのサービスをクライアントサイドのJavaScriptからでも操作できるようになります。つまり、IaaSとして利用できるAWSをサーバサイドのプログラム無しでBaaSのように扱うことができます。最も有名なBaaSの一つであるParse.comもバックエンドにはAWSを利用していることが知られていますが、今回登場したAWS SDK for JavaScriptを用いるとParse.comのようなBaaSと同じようなことがAWSでもできるのではないかと注目されているようです。

この記事では、AWSで提供されているファイルストレージのS3と分散キーバリューストアのDynamoDBを、AWS SDK for JavaScriptを使ってクライアントサイドJavaScriptから扱う例を紹介します。

Amazon S3をJavaScriptから扱う

ファイルストレージのS3をJavaScriptから扱う例を最初に紹介します。

Facebookアカウントで認証してS3にファイルをアップロードする例を紹介します。その次に、埋め込みのクレデンシャルを用いて認証無しでS3を操作する例を紹介します。

S3バケットを作成する

まずは、 JavaScriptで操作するためのS3バケットを作成します。

AWS Management Consoleにアクセスして、S3の管理画面に移動します。「Create Bucket」をクリックして、JavaScriptから操作するS3のバケットを作成します。

S3のバケットを作成したら、次にそのS3に対して他のドメインからAjaxでアクセスできるようにするために、CORS(Cross Origin Resource Sharering)を設定します。作成したBucketの詳細からPermissionsを選択して、「Add CORS Configulation」をクリックすると、CORSを設定ダイアログが表示されます。


ここに、以下の設定を貼り付けて保存してください。


<?xml version="1.0" encoding="UTF-8"?>
<CORSConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
    <CORSRule>
        <AllowedOrigin>*</AllowedOrigin>
        <AllowedMethod>GET</AllowedMethod>
        <AllowedMethod>PUT</AllowedMethod>
        <AllowedMethod>POST</AllowedMethod>
        <AllowedMethod>DELETE</AllowedMethod>
        <AllowedHeader>*</AllowedHeader>
    </CORSRule>
</CORSConfiguration>

この設定では、どんなドメインからでもajaxでリクエストを投げられるようになります。認証は別にあるので、これでも一応問題ありませんが気になる場合はAllowOrigin要素内に自分がアクセスするオリジンを指定するようにしてください。

Facebookアプリを作成する

ユーザをFacebookアカウントで認証するには、まずFacebookアプリの情報を設定しておく必要があります。

Facebook Developersのアプリ一覧にアクセスして、「新しいアプリを作成」をクリックします。Facebookアプリのデベロッパではない人はデベロッパとして登録しておいてください。

アプリを作成した後は、アプリを通じてウェブサイトでFacebook認証を行うための設定を追加します。「Facebookでログインが可能なウェブサイト」の欄に、AWS SDK for JavaScriptを使うサイトのドメインを設定します。

Facebook認証でS3を操作するためのパーミッションを追加する

次に、Facebookアカウントで認証したユーザがS3を扱えるための設定を追加します。IAMの管理画面にアクセスして、左のメニューのRolesをクリックします。

「Create New Role」をクリックすると、ロールを新規作成するためのダイアログが表示されます。ロールの種類を選択するダイアログが表示されるので、新しいロールを作成します。Select Role Typeでは、Role for Web Identity Provider Accessを選択します。次の画面では、認証に利用するIdentityProviderを選択します。「Facebook」を選択して、Application Idの欄では、先ほど作成したFacebookアプリのApp IDを入力します。




次に表示されるポリシーの選択では、Custom Policyを選択します。Policy Nameには適当な名前を入力して、Policy Documentには以下のポリシーを設定してください。(バケット名)には、先ほど作成したS3のバケット名を入れます。


{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Action": [
        "s3:PutObject",
        "s3:PutObjectAcl"
      ],
      "Resource": [
        "arn:aws:s3:::(バケット名)/facebook-${graph.facebook.com:id}/*"
      ],
      "Effect": "Allow"
    }
  ]
}

このポリシーでは、バケット以下に作成したfacebook-(ユーザのfacebookのID)ディレクトリ以下のフォルダにアップロードすることを許可するポリシーです。このポリシーを追加することで、facebookアカウントで認証したユーザが各々アップロードできるディレクトリを隔離できます。

作成したロールのARNも後で使うので確認して下さい。ロールを選択して詳細情報を見るとARNが書いてあります。

JavaScriptを記述する

Facebookアカウントで認証してS3へファイルをアップロードする例が以下になります。


<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Facebook認証でのS3アップロード</title>
<script type="text/javascript" src="https://sdk.amazonaws.com/js/aws-sdk-2.0.0-rc1.min.js"></script>
</head>
<body>
<h1>Facebook認証でのS3アップロード</h1>
<input type="file" id="file-chooser" /> 
<button id="upload-button" style="display: none">Upload to S3</button>
<div id="results"></div>
<div id="fb-root"></div>
<script type="text/javascript">
// FacebookアプリのApp ID
var appId = 'xxxxxxxxxxxxxxxxxxxx';
// ロールのARN
var roleArn = 'arn:aws:iam::xxxxxxxxxxxx:role/HogeRole';
// S3のバケット名
var bucketName = '(バケット名)';
var fbUserId;
var bucket = new AWS.S3({params: {Bucket: bucketName}});
var button = document.getElementById('upload-button');
button.addEventListener('click', function() {
    var fileChooser = document.getElementById('file-chooser');
    var results = document.getElementById('results');
    var file = fileChooser.files[0];
    if (file) {
        results.innerHTML = '';
        var objKey = 'facebook-' + fbUserId + '/' + file.name;
        var params = {Key: objKey, ContentType: file.type, Body: file, ACL: 'public-read'};
        bucket.putObject(params, function (err, data) {
            if (err) {
                results.innerHTML = 'ERROR: ' + err;
            } else {
                alert('アップロードされました');
            }
        });
    }
}, false);
/*
 * Facebook認証処理
 * https://developers.facebook.com/docs/javascript/gettingstarted/
 */
window.fbAsyncInit = function() {
    FB.init({ appId: appId });
    FB.login(function(response) {
        bucket.config.credentials = new AWS.WebIdentityCredentials({
            ProviderId: 'graph.facebook.com',
            RoleArn: roleArn,
            WebIdentityToken: response.authResponse.accessToken
        });
        fbUserId = response.authResponse.userID;
        button.style.display = 'block';
    });
};
// Facebook SDKを読み込む
(function(d, s, id){
    var js, fjs = d.getElementsByTagName(s)[0];
    if (d.getElementById(id)) {return;}
    js = d.createElement(s); js.id = id;
    js.src = "//connect.facebook.net/ja_JP/all.js";
    fjs.parentNode.insertBefore(js, fjs);
}(document, 'script', 'facebook-jssdk'));
</script>
</body>
</html>

このHTMLをfacebookアプリで設定したドメインに設置してアクセスしてください。facebookの認証のためのダイアログが表示され、認証が済むとアップロードフォームが表示されます。このフォームでファイルをアップロードすると、S3のバケットにアップロードされます。アップロードしたファイルは、S3のダッシュボードで確認できます。

埋め込みのクレデンシャルで認証なしでS3を操作する

今度は、Facebookアカウントで認証するのではなく、予め発行したクレデンシャルをウェブページに埋め込んで認証なしでS3を扱う方法を紹介します。

この方法では、ウェブページにクレデンシャルを埋め込む形式のため、秘密にしなければいけないアクセスキーが容易に盗まれてしまいます。従って、一般に公開する用途には向いていません。限られた人だけが利用するバックエンド用のページ内などで使うことを想定してください。

まずIAMの管理画面にアクセスして、ウェブページに埋め込むクレデンシャルのための新しいユーザを作成します。左メニューのUsersをクリックして、Create New Usersをクリックしてください。

適当な名前を入力して、次に進むとユーザとそれに対応するアクセスキーが生成されます。Show User Security Credentialsをクリックして、Access Key IDとSecret Access Keyをメモしておいてください。このユーザの権現を利用したいときには、このアクセスキーが必要となります。

作成したユーザがS3を操作できるパーミッションを追加します。先ほど作成したユーザを選択してPermissions → Attach User PolicyをクリックしてCustom permissionをクリックします。ここでは、以下の様なポリシーを追加します。(バケット名)にはあなたのバケット名を入れてください。


{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Action": [
        "s3:*"
      ],
      "Resource": [
        "arn:aws:s3:::(バケット名)"
      ],
      "Effect": "Allow"
    }
  ]
}

IAMで作成したユーザのARNも後で利用するのでメモしておいてください。

S3の方にもパーミッションの設定があるのでこれも追加します。S3のバケットのPropertiesを開いて、PermissionsからEdit policyを選択すると、そのバケットのポリシーを編集するダイアログが開きます。ここに、先ほど追加したユーザがこのバケットの編集を許可するための設定を追加します。


{
    "Version": "2008-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Principal": {
                "AWS": "(IAMで作成したユーザのARN)"
            },
            "Action": "s3:*",
            "Resource": "arn:aws:s3:::(バケット名)/*"
        }
    ]
}

このポリシーでは、指定したユーザに対して先ほど作成したバケットへのS3の全てのアクションを許可しています。

ようやく準備が整ったのでS3をJavaScriptから扱うコードを記述します。Facebook認証する箇所が無い代わりに、先ほど作成したユーザのクレデンシャルを埋め込んでいることに注意ください。


<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>埋め込みクレデンシャルでのS3アップロード</title>
<script type="text/javascript" src="https://sdk.amazonaws.com/js/aws-sdk-2.0.0-rc1.min.js"></script>
</head>
<body>
<h1>埋め込みクレデンシャルでのS3アップロード</h1>
<input type="file" id="file-chooser" /> 
<button id="upload-button">Upload to S3</button>
<div id="results"></div>
<script type="text/javascript">
// S3のバケット名
var bucketName = '(バケット名 )';
// ユーザ作成時のアクセスキーを設定
AWS.config.update({accessKeyId: '(アクセスキー)', secretAccessKey: '(シークレットアクセスキー)'});
var bucket = new AWS.S3({params: {Bucket: bucketName}});
var button = document.getElementById('upload-button');
button.addEventListener('click', function() {
    var fileChooser = document.getElementById('file-chooser');
    var results = document.getElementById('results');
    var file = fileChooser.files[0];
    if (file) {
        results.innerHTML = '';
        
        var objKey = file.name;
        var params = {Key: objKey, ContentType: file.type, Body: file, ACL: 'public-read'};
        
        bucket.putObject(params, function (err, data) {
            if (err) {
                results.innerHTML = 'ERROR: ' + err;
            } else {
                var params = {Bucket: bucketName, Key: objKey};
                bucket.getSignedUrl('getObject', params, function (error, url) {
                    if (error) {
                        alert(error);
                    } else {
                        var html = "<a href='" + url + "'>ファイルがアップロードされました</a>";
                        results.innerHTML += html;
                    }
                });
            }
        });
    }
}, false);
</script>
</body>
</html>

クレデンシャルの埋め込みでは、以下のようにアクセスキーを直接指定しています。IAMで作成したユーザのアクセスキーを指定してください。


AWS.config.update({accessKeyId: 'アクセスキー', secretAccessKey: 'シークレットアクセスキー'});

 

DynamoDBをJavaScriptから扱う

DynamoDBは、AWS上で利用できる分散NoSQLデータベースです。DynamoDBで扱うデータは、通常のリレーショナルデータベースとは違ってスキーマレスで、事前にテーブルのメンバーを定義しておく必要がありません。さらにDynamoDBに保存したデータは自動的に複数のサーバにレプリケーションされます。

ここでは、まずS3の例と同様にFacebookアカウントで認証してDynamoDBにデータを保存したり取得する例を紹介します。次に埋め込みのクレデンシャルを用いて認証なしでDynamoDBを扱う例を紹介します。

DynamoDBのテーブルを作成する

扱うDynamoDBのテーブルを作成します。DynamoDBでは、保存するデータを分散するためのhashkeyとrangekeyのカラム名を指定できます。hashkeyの値が分散すればするほどデータの取得や保存を効率良く行えます。ここでは、hashkeyにuserid、rangekeyにはcreatedAtを設定します。DynamoDBでは、テーブルのカラムのインデックスをつけたり、処理量を監視するアラートを設定できますが、今回はインデックスやアラートなどのオプションは一切付けずにテーブルを作成します。


Facebook認証でDynamoDBを操作するためのパーミッションを追加する

DynamoDBのテーブルを作成した次は、Facebook認証でDynamoDBを扱うためのパーミッションを設定します。先ほど作成したRoleに新しいPolicyを追加します。DynamoDBのテーブル一覧画面でAccess Controlボタンをクリックすると、Facebook認証でテーブルのパーミッションを追加するためのダイアログが表示されます。



{
  "Version": "2012-10-17",
  "Statement": [{
      "Effect": "Allow",
      "Action": [
        "dynamodb:BatchGetItem",
        "dynamodb:BatchWriteItem",
        "dynamodb:DeleteItem",
        "dynamodb:GetItem",
        "dynamodb:PutItem",
        "dynamodb:Query",
        "dynamodb:UpdateItem"
      ],
      "Resource": ["(作成したDynamoDBのテーブルのARN)"],
      "Condition": {
        "ForAllValues:StringEquals": {
          "dynamodb:LeadingKeys": ["${graph.facebook.com:id}"]}
    }
  }]
}

表示されたポリシーをコピーし、S3のFacebook認証時に使ったロールに対して新しいポリシーを追加します。IAMのダッシュボードから先ほど作成したRoleを選択し、Add attach policyでコピーしたポリシーを登録してください。

JavaScriptでDynamoDBにデータを追加する

JavaScriptからDynamoDBにデータを保存し、リストする例が以下になります。ロールのARNやFacebook App IDやDynamoDBのリージョンには適切な値を入れてください。


<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Facebook認証でDynamoDBを操作する</title>
<script type="text/javascript" src="https://sdk.amazonaws.com/js/aws-sdk-2.0.0-rc1.min.js"></script>
</head>
<body>
<h1>Facebook認証でDynamoDBを操作する</h1>
<button onclick="javascript:add()">+TODO</button>
<div id="TODOList"></div>
<script type="text/javascript">
// FacebookアプリのApp ID
var appId = 'xxxxxxxxxxxxxxxxxxxx';
// ロールのARN
var roleArn = '(ロールのARN)';
// DynamoDBのテーブル名
var tableName = '(テーブル名)';
// DynamoDBテーブルのリージョン
var region = 'ap-northeast-1';
var fbUserId;
var table = new AWS.DynamoDB({params: {TableName: 'fuga'}, region: region});
function add() {
    var itemParams = {
        Item: {
            userid: {S: fbUserId}, 
            createdAt: {S: '' + new Date().getTime()}, 
            todo: {S: prompt("TODO")}
        }
    };
    table.putItem(itemParams, function(error) {
        if (error) {
            alert("Error: " + error);
        } else {
            alert("追加しました");
            load();
        }
    });
}
function load() {
    table.query({
        AttributesToGet : ['todo'],
        KeyConditions:
        {
            userid : {
                AttributeValueList: [
                    {S: fbUserId, }
                ],
                "ComparisonOperator": "EQ"
            }
        }
    }, function(error, data) {
        if (error) {
            alert(error);
        } else {
            var wrapper = document.getElementById('TODOList');
            wrapper.innerHTML = '';
            data.Items.map(function(item) {
                var p = document.createElement('p');
                p.textContent = item.todo.S;
                return p;
            }).forEach(function(p) {
                wrapper.appendChild(p);
            });
        }
    });
}
</script>
<div id="fb-root"></div>
<script type="text/javascript">
/*
 * Facebook認証処理
 * https://developers.facebook.com/docs/javascript/gettingstarted/
 */
window.fbAsyncInit = function() {
    FB.init({ appId: appId });
    FB.login(function(response) {
        table.config.credentials = new AWS.WebIdentityCredentials({
            ProviderId: 'graph.facebook.com',
            RoleArn: roleArn,
            WebIdentityToken: response.authResponse.accessToken
        });
        fbUserId = response.authResponse.userID;
        load();
    });
};
// Facebook SDKを読み込む
(function(d, s, id){
    var js, fjs = d.getElementsByTagName(s)[0];
    if (d.getElementById(id)) {return;}
    js = d.createElement(s); js.id = id;
    js.src = "//connect.facebook.net/ja_JP/all.js";
    fjs.parentNode.insertBefore(js, fjs);
}(document, 'script', 'facebook-jssdk'));
</script>
</body>
</html>

 

埋め込みのクレデンシャルでDynamoDBを扱う

今度は、埋め込みのクレデンシャルを用いてDyanmoDBも操作します。作成したユーザに対して、新しくDynamoDBを扱うためのパーミッションを設定します。ユーザのPermissions → Attach User Policyを選択して以下のポリシーを追加します。


{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Action": [
        "dynamodb:*"
      ],
      "Effect": "Allow",
      "Resource": "*"
    }
  ]
}

このポリシーでは、DynamoDBに関する全ての動作を許可するようになっています。実際に運用する際には、許可するアクションやリソースの指定は厳しく行って下さい。

認証には、facebook認証のためのコードを取り除いて、パーミッションを設定したこのユーザのクレデンシャルをS3の時と同様に以下のように埋め込んでください。


AWS.config.update({accessKeyId: 'アクセスキー', secretAccessKey: 'シークレットアクセスキー'});

 

終わりに

この記事では、AWS SDK for JavaScriptを使ってS3やDynamoDBを扱う例を紹介しました。

今のところAWS SDK for JavaScriptで利用できるのはAWSの中でもS3やDynamoDBやSNSなどですが、順次利用できるサービスは増えていくものと思われます。

参考