Windows Azure ストレージ: CORS の導入
このポストは、2 月 3 日に Windows Azure Storage Team が投稿した Windows Azure Storage: Introducing CORS の翻訳です。
マイクロソフトは、先日、Windows Azure ストレージ用 CORS (クロス オリジン リソース共有) をリリースしました。CORS (英語) は BLOB、テーブル、およびキューの各サービスでサポートされ、Windows Azure ストレージ クライアント ライブラリ 3.0 (英語) を使用すると各サービスに対して有効化できます。今回の記事では、CORS について説明すると共に、Windows Azure ストレージで CORS をサポートする方法をサンプルを交えて紹介します。完全なサンプル コードは、こちらのページ (英語) からダウンロードできます。
CORS の概要
Web ブラウザー (ユーザー エージェントとも呼ばれる) では、通常、同一呼び出し元ポリシーによる制限がネットワーク要求に対して適用されています。この制限は、セキュリティ上の理由により、特定のドメインから実行されるクライアント側の Web アプリケーションが他のドメインに要求を発行することを禁止するものです。たとえば、https://www.contoso.com/ のサイト内にある JavaScript のコードが https://www.northwindtraders.com/ などの他のドメインに要求を発行することは、Web ブラウザーがこのような要求の実行を禁止しているためできません。
CORS は、上記のような制限を緩和し、ドメイン間で互いにアクセス許可を付与して、互いのリソースにアクセスできるようにするメカニズムです。上記の例で言うと、ユーザー エージェントは、www.contoso.com というドメインから読み込まれた JavaScript コードが www.northwindtraders.com にあるリソースにアクセスできるように、追加のヘッダーを送信します。このとき、後者のドメインは、www.contoso.com から自身のリソースへのアクセスを許可または拒否することを示す追加のヘッダーを返すことができます。
CORS では HTTP の仕様が拡張されます。詳細については https://www.w3.org/TR/cors/ を参照してください。
Windows Azure ストレージに CORS を導入した理由
CORS を使用しない場合、Windows Azure ストレージ サービスを使用する Web サイトは、ストレージを呼び出す際にプロキシとして働く必要があります。ストレージへの要求はすべてサービス経由でプロキシ転送される必要があるため、読み込み量が増加すると、サーバーのスケール アウトが必要になります。例として、https://www.contoso.com という Web サイトを所有し、その Web サイトにユーザーが BLOB をアップロードする機能があり、その BLOB を Windows Azure ストレージ アカウントである contoso.blob.core.windows.net というドメインに永続化する場合を考えます。この場合、まずブラウザーからサービスへアップロードが行われ、次にサービスがそのデータをストレージ アカウントへ PUT することになります。このため、所有する Web サイトへの上り方向のトラフィック量が増加すると、このサービスをスケール アウトする必要があるのです。
しかし、CORS を使用すると、アップロード時にサービスを経由する必要がなくなります。この例では、ストレージ アカウントの contoso.blob.core.windows.net で CORS を有効化し、contoso.com からのアクセスを許可します。そうすると、プロキシ サービスを挟まないでも、Javascript コードが共有アクセス署名を使用してストレージ アカウントに BLOB を直接アップロードできるようになります。これで、所有する Web サイトへの上り方向のトラフィックがどれだけ増加してもサービスをスケール アウトする必要はなく、Windows Azure ストレージ サービスの非常に大規模なスケールを活用することができます。
ここで、CORS は認証メカニズムではないという点に注意してください。CORS が有効な場合にストレージのリソースに対して発行される任意の要求は、認証用の適切な署名を持っているか、またはパブリックなリソースに対するものである必要があります。
Windows Azure ストレージ用の CORS の詳細については、以下のページに掲載されている MSDN のドキュメントを参照してください。 https://msdn.microsoft.com/ja-jp/library/windowsazure/dn535601.aspx
CORS のしくみ
通常、CORS の動作には 2 つの段階があります。まず、プレフライト要求 (OPTIONS 要求) が発行され、その後で実際の要求 (GET、PUT、POST など) が続きます。
ただし、GET などの単純なメソッド (英語) で簡単な要求 (英語) を発行する場合などでは、ユーザー エージェントがプレフライト要求を発行しないこともあります。
プレフライト要求
プレフライト要求とは、サービスごとに設定されている CORS の制限を照会するしくみです。Javascript のコードの一部が別のドメインへの要求を送信するたびに、ユーザー エージェントはこの要求を発行します。プレフライト要求は OPTIONS 要求の一種で、実際の要求ヘッダー、目的の HTTP 要求、および発信元のドメインを伝達します。受信側のサービスは、発信元、要求ヘッダー、メソッドなどの制限を定めた、事前構成済みの一連のルールに基づいて、目的の処理を評価し、要求を受理または拒否します。Windows Azure ストレージの場合、CORS ルールはサービスごと (BLOB、テーブル、キュー) に構成され、プレフライト要求も個々のリソースごとではなくサービスごとに評価されます。
通常、プレフライト要求はブラウザーによってキャッシュされるため、キャッシュされたプレフライト要求の処理が正常に完了し、実際の要求がディスパッチされる際、後続の要求ではこの手順は省略されます。キャッシュの TTL (Time to Live) はユーザーが設定できます。
実際の要求
実際の要求とは、クライアントが目的とする要求のことです。この要求には、ユーザー エージェントが追加した “Origin” というヘッダーが存在します。
受理された CORS 要求に対しては、有効な Access-Control 応答ヘッダーが返されます。Access-Control ヘッダーが返されない場合、ブラウザーはこの応答を確認し、要求の発行者を拒否します。ここで、Origin ヘッダーが構成済みのルールを満たしているかどうかにかかわらず、サービスは要求の処理を継続する点に注意が必要です。Web ブラウザーは、Access-Control 応答ヘッダーの値に従って、応答のどの部分 (必要な場合) を JavaScript に渡すかを決定します。
また、OPTIONS 要求が必須ではない場合もあります。この場合は、Web ブラウザーがこの要求を発行せず、代わりに実際の要求が直接ディスパッチされます。このような要求は簡単な要求 (英語) と呼ばれます。
Windows Azure ストレージでの CORS の活用シナリオ
先に述べたように、BLOB で CORS を使用する一般的な例としては、Web ブラウザーから Windows Azure ストレージ アカウントへファイルを直接アップロードする場合が考えられます。このとき、CORS と SAS (共有アクセス署名) 認証メカニズムを併用すると、Web ブラウザーに対してストレージ アカウントへの書き込み権限を付与することができます。このケースでは、ユーザーがファイルをアップロードする準備が完了するたびに、JavaScript コードは、アップロードする BLOB の SAS の URL をサービスに対して要求し、次に、その BLOB を PUT する要求をストレージに対して実行します。この際、セキュリティ リスクを低減し、特定のコンテナーまたは BLOB (あるいはその両方) のみがアップロードされるように、SAS トークンのアクセス時間を必要な長さに制限することを推奨します。SAS の使用に関するベスト プラクティスについては、こちらのページ (英語) を参照してください。
他には、Windows Azure テーブルで CORS を使用して、ブラウザーでテーブルのデータを表示し、必要に応じてそのデータを操作するという場合があります。その典型的な例として、管理ページを公開し、Windows Azure テーブルで永続化されている Web サイトの構成設定をユーザーが変更できるようにするというケースが挙げられます。このような機能の実装には CORS、SAS、および JSON が使用できます。詳細については、こちらのサンプル コード (英語) を参照してください。このケースで JavaScript の JQuery の機能を利用して、Windows Azure テーブルへの要求の実行とデータの操作を行う場合、JSON を使用するのが最適です。
Windows Azure ストレージ チームが提供している JavaScript のサンプル コード (英語) では、BLOB のアップロードとリスト表示を行う方法や、JQuery を使用して Windows Azure テーブルに対して Insert Entity や Query Entities の操作を行う方法を示しています。
Windows Azure ストレージで CORS を有効化する方法
CORS を使用するには、まず、目的のサービス (BLOB、テーブル、キュー) ごとに個別に有効化し、構成する必要があります。REST API または Windows Azure ストレージ クライアント (.Net (英語)、Java (英語)、C++ (英語) のいずれか) を使用して CORS ルールを設定するには、ストレージの最新バージョンである “2013-08-15” バージョンを使用する必要があります。いったん有効化した後は、CORS に準拠している限り、ストレージ アカウントに対する操作および REST 要求 (Put Blob や Query Entities など) の発行には、任意のバージョンのストレージを使用できます。
こちらのページに掲載されている MSDN のドキュメントでは、REST API の Set Blob Service Properties (REST API)、Set Queue Service Properties (REST API)、および Set Table Service Properties (REST API) を使用して CORS ルールを構成する方法を説明しています。
このセクションでは、CORS ルールの構成例を簡単に説明し、Windows Azure ストレージ クライアント .NET ライブラリ 3.0 (英語) を使用して CORS の有効化および構成を行う方法を紹介します。
CORS ルールは、BLOB、テーブル、キューの各ストレージ サービスでそれぞれ 5 個まで構成できます。次に示すのは、CORS ルールを 1 つだけ構成した場合の例です。
<CorsRule>
<AllowedOrigins>https://www.contoso.com, https://www.fabrikam.com</AllowedOrigins>
<AllowedMethods>PUT,GET</AllowedMethods>
<AllowedHeaders>x-ms-meta-data*,x-ms-meta-target,x-ms-meta-source</AllowedHeaders>
<ExposedHeaders>x-ms-meta-*</ExposedHeaders>
<MaxAgeInSeconds>200</MaxAgeInSeconds>
</CorsRule>
各 XML 要素の詳細な説明については、こちらの MSDN ドキュメントを参照してください。
上に示したルールでは、次の条件を満たす CORS 要求を許可します。
- 発信元が https://www.contoso.com または https://www.fabrikam.com のいずれか (AllowedOrigins)
- かつ、PUT または GET の HTTP 要求を含む (AllowedMethods)
AllowedOrigins と AllowedMethods は、両方とも "*" を使用して設定することもできます。これは最も一般的な使用方法で、サービスは、あらゆる発信元から呼び出された、あらゆるメソッド (PUT、POST、GET など) に対して必要な CORS ヘッダーを送信します。しかし、この場合でも、誰もがユーザーのアカウントにアクセスできるわけではなく、先に述べたように適切な認証署名が必要です。
AllowedHeaders には、クライアント (JavaScript コード) がストレージ アカウントに送信可能な要求ヘッダーを指定します。プレフライト要求または実際の要求のいずれかに対して Windows Azure ストレージ サービスから返される Access-Control-Allow-Headers 応答ヘッダーには、許可されるヘッダーのリストが含まれています。これにより、Web ブラウザーは許可されたヘッダーを把握し、AllowedHeaders の構成を強制適用します。ブラウザーから呼び出しが発行されたときにクライアントにメタデータ情報をまったく保存しないようにして、同時に、x-ms-meta-reserved キーに含まれるすべての情報の保存を許可しない場合、上記の例のように、x-ms-meta-data から始まる、あるいは x-ms-meta-target または x-ms-meta-source と完全一致するキーの名前のみにメタデータを制限します。通常は、すべての要求のヘッダーを許可するように、この構成には * を設定します。
ExposedHeaders は、クライアントの JavaScript コードに対して公開できる応答ヘッダーを指定するものです。この構成も、Web ブラウザーが強制適用します。実際の要求に対して Windows Azure ストレージ サービスから返される Access-Control-Expose-Headers 応答ヘッダーには、指定されたヘッダーのリストが含まれています。これにより、Web ブラウザーは指定されたヘッダーを把握し、ExposedHeaders の構成を強制適用します。通常は、すべての応答のヘッダーを許可するように、この構成には * を設定します。
MaxAgeInSeconds は、プレフライトの OPTIONS 要求のキャッシュをブラウザーが保持できる時間の最大の長さを指定します。上記の例では、200 秒に設定されています。ルールを頻繁に変更しない場合、MaxAgeInSeconds には多少大きめの値を設定することを推奨します。こうすることにより、ラウンドトリップが減少して Web アプリケーションの応答性が向上し、また有料であるプレフライト要求が減少してストレージの使用料金が抑えられるという効果があります。
CORS ルールの評価は、REST API の Set Service Properties の XML で出現する順に実行されます。このため、最も厳しい条件を最初に配置し、* を指定したルールはすべて最後に配置します。CORS ルールの評価の詳細については、MSDN のドキュメントの「CORS ルールの評価ロジックについて」を参照してください。
Windows Azure ストレージクライアントライブラリ 3.0 を使用して CORS を有効化するコードのサンプル
ここからは、Windows Azure ストレージ アカウントの BLOB サービスおよびテーブル サービスで CORS を有効化するコードのサンプルについて説明します。このコードは、こちらのページ (英語) で公開しているサンプル コードの一部です。
このコードは、まず現在のサービスのプロパティを GET し、すべての発信元および HTTP メソッド (PUT、GET、HEAD、POST) を許可するという 1 つのルールに従って CORS を有効化します。その後、変更が適用されたサービスのプロパティを BLOB およびテーブルの両サービスにコミットします。
private static void InitializeCors()
{
// サービスの起動後 CORS を有効化
// BlobClient を指定し、現在のサービスのプロパティをダウンロード
ServiceProperties blobServiceProperties = BlobClient.GetServiceProperties();
ServiceProperties tableServiceProperties = TableClient.GetServiceProperties();
// CORS を有効化し構成
ConfigureCors(blobServiceProperties);
ConfigureCors(tableServiceProperties);
// CORS の変更をサービスのプロパティにコミット
BlobClient.SetServiceProperties(blobServiceProperties);
TableClient.SetServiceProperties(tableServiceProperties);
}
private static void ConfigureCors(ServiceProperties serviceProperties)
{
serviceProperties.Cors = new CorsProperties();
serviceProperties.Cors.CorsRules.Add(new CorsRule()
{
AllowedHeaders = new List<string>() { "*" },
AllowedMethods = CorsHttpMethods.Put | CorsHttpMethods.Get | CorsHttpMethods.Head | CorsHttpMethods.Post,
AllowedOrigins = new List<string>() { "*" },
ExposedHeaders = new List<string>() { "*" },
MaxAgeInSeconds = 1800 // 30 分
});
}
BLOB を Windows Azure ストレージにアップロードする JavaScript のサンプルコード
以下に示すサンプルは、Web ブラウザーから Windows Azure ストレージにファイルを直接アップロードする JavaScript のコードの一部です。コード全文はこちらのページ (英語) で公開しています。次のコードは UploadImage.cshtml ファイルの一部です。
// BLOB の SAS の URL を使用して BLOB を Azure ストレージにアップロードするメソッド
// Web ブラウザーが、必要な CORS ヘッダーを追加し、必要に応じてプレフライト要求を発行
// blobSasUrl: BLOB の SAS の URL。自身のサービスに対する Ajax の呼び出しにより取得済み
// fileDataAsArrayBuffer: アップロードするファイルの生データを含む ArrayBuffer (Byte 配列)
function uploadImage(blobSasUrl, fileDataAsArrayBuffer) {
var ajaxRequest = new XMLHttpRequest();
// 画像が正常にアップロードされた後、その画像を表示する renderImage を呼び出す
ajaxRequest.onreadystatechange = function() {return renderImage(ajaxRequest, blobSasUrl)};
try {
// ストレージに対し Put Blob (ブロック BLOB) を実行
ajaxRequest.open('PUT', blobSasUrl, true);
ajaxRequest.setRequestHeader('Content-Type', 'image/jpeg');
ajaxRequest.setRequestHeader('x-ms-blob-type', 'BlockBlob');
ajaxRequest.send(fileDataAsArrayBuffer);
}
catch (e) {
alert("サーバーに画像をアップロードできませんでした。\n" + e.toString());
}
}
次のコードでは、最初に ASP.NET サービスを呼び出して BLOB の SAS の URL を取得し、最終的には上記の uploadImage メソッドを呼び出します。まず、指定されたファイルを読み込み、次に、BLOB の SAS の URL を取得します。最後に uploadImage を呼び出し Azure ストレージに画像をアップロードします。
// このメソッドは、アップロード対象の画像をユーザーが選択した後に呼び出される
// 選択されたすべてのファイルを対象としてループ処理し、1 つずつ Azure にアップロード
function handleFileSelect(evt) {
var files = evt.target.files; // 選択されたすべてのファイル (FileList オブジェクト)
// FileList をループ処理し、保存してファイルのサムネイルを表示
for (var i = 0, file; file = files[i]; i++) {
// ファイルのデータ全体を ArrayBuffer として読み込むための reader を作成
var reader = new FileReader();
// Azure BLOB の SAS の URL を要求する際に使用される AJAX の URL
file.getSasUri = "/Home/GetBlobSasUrl" + "?blobName=" + file.name;
reader.onloadend = (function (theFile) {
return function (e) {
// reader がファイルの読み込みを終了した後
// サービスに対して AJAX の呼び出しを発行し、SAS の URL を取得
$.ajax({
type: 'GET',
url: theFile.getSasUri,
success: function (res, status, xhr) {
// GetBlobSasUrl を呼び出して、必要な BLOB の SAS を生成
blobSasUrl = xhr.responseText;
// これで、画像のアップロードに使用する SAS の URL の取得が完了
// この SAS の URL を渡し、ArrayBuffer をアップロード
uploadImage(blobSasUrl, e.target.result);
},
error: function (res, status, xhr) {
alert("サーバーから SAS を取得できませんでした。");
}
});
};
})(file);
// 画像ファイルを ArrayBuffer として読み込み、完了後に reader.onloadend イベントが発生
reader.readAsArrayBuffer(file);
}
}
Windows Azure テーブルにアクセスする JQuery のサンプルコード
こちらのページ (英語) で、Insert Entity および Query Entities 操作を行う JQuery のサンプル コードを公開しています。まずは InsertTableEntities.cshtml と QueryTableEntities.cshtml の両ファイルからご覧ください。
CORS のサンプル コード
CORS のサンプル コードはこちらのページ (英語) で公開されています。最新バージョンの NuGet Package Manager (英語) がインストールされていて、参照先のライブラリにアクセスできることを確認してください。
既定では、このサンプルは Windows Azure ストレージ エミュレーターを使用するように構成されています。エミュレーターはこちらのページからダウンロードできます。AzureCommon.cs ファイルの中で AzureCommon.StorageAccount を構成すると、Windows Azure ストレージ アカウントを使用できるようになります。
まとめ
簡単にまとめると、Windows Azure ストレージで CORS をサポートすると、アプリケーション開発者は、自身の Web ページでスクリプトを使用して、Web サービスからの呼び出しをプロキシ転送せずに Windows Azure ストレージの各リソース (BLOB、テーブル、キュー) と直接やり取りすることができるようになります。また、Windows Azure ストレージ クライアント ライブラリ (英語) を使用すると、CORS を簡単に構成できます。
Wael Abdelghani、Jean Ghanem
参考資料
MSDN で公開されている CORS のサンプル コード (英語)
Windows Azure ストレージのリリース - CORS、JSON、分単位メトリックなど各種機能の導入
2013-08-15 REST バージョンのストレージのリリース ノート
Windows Azure ストレージ サービスでのクロス オリジン リソース共有 (CORS) のサポート
Windows Azure ストレージのライブラリ