2012年9月5日水曜日

S3ってなんじゃ?(CORS対応)

ごぶさたしております。
S3がCORS対応になったので、少し触れてみたいと思います。

CORSはCrossDomainResourceSharingの略です。
JSONPなどの特殊なケースを除いて通常ajaxなどではクロスドメイン通信は認められていません。
CORSは、通信先のサーバーで条件付きで許可をすることでクロスドメインアクセスを可能にするための仕組みで、W3Cで策定されいてる仕様です。モダンブラウザであればほぼサポートされているかと思います。

CORSではブラウザがクロスドメインのサーバーにリクエストする際に、事前にそのサーバーがこれから行おうとしているリクエストを許可しているかどうかをHTTPメソッドのひとつであるOPTIONSメソッドといくつかのHTTPリクエストヘッダを用いて問い合わせます。これをPreflightリクエストと言います。
そしてそのレスポンスをもってブラウザは通信可能かどうかを判断します。

Preflightリクエストで使用されるヘッダは
  • Origin 
    • リクエスト元のオリジン
    • (http://www.example.com/foo/barからリクエストされる場合、http://www.example.com/)
  • Access-Control-Request-Method 
    • これからリクエストするときに使用するHTTPメソッド
  • Access-Control-Request-Headers
    • これからリクエストするときに使用するHTTPヘッダー

などです。これらは通常ブラウザが自動で判別してリクエストヘッダに付与します。
それに対してレスポンスヘッダには以下の内容がセットされます。
  • Access-Control-Allow-Origin
    • この項目で指定されたOriginにリクエストが許可されます。
  • Access-Control-Allow-Credentials
    • trueならCookieなどユーザーの資格情報をレスポンスに含めることを許可します
  • Access-Control-Expose-Headers
    • CORS API仕様として公開して良いヘッダーフィールド
  • Access-Control-Max-Age
    • このPreflightリクエストの結果のキャッシュ保持秒数
  • Access-Control-Allow-Methods
    • 許可するHTTPメソッドが含まれます
  • Access-Control-Allow-Headers
    • 許可するリクエストヘッダーが含まれます

ブラウザはこのレスポンスヘッダを見てリクエスト可能かどうかを判断します。
通常REST APIなどを実装しWEBサービスを行うサーバーでは、このCORSに対応するような実装をすることで許可した相手にのみAPIを利用できるように実装を行う必要がありますが、今回S3ではそれが簡単な設定を行うだけで利用できるようになりました。

それでは実際にS3のCORS機能を使って具体的にどんなことが行われているのかを見てみます。

まずS3にバケットをつくります。
そこに例として以下のJSONファイルをアップロードします。

{
   "id": "1405028029",
   "name": "Satoru Miura",
   "first_name": "Satoru",
   "last_name": "Miura",
   "link": "https://www.facebook.com/memocra",
   "username": "memocra",
   "gender": "male",
   "locale": "ja_JP"
}

そしてEC2などで立ち上げた別のホストにこのjsonファイルをajaxで読み込む以下のようなHTMLを配置します。

<html>
  <head>
    <script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js" ></script>
    <script type="text/javascript">
    $(function(){
      $.ajax({
        url:'http://s3-ap-northeast-1.amazonaws.com/memorycraft/memocra.json',
        type:'GET',
        contentType:"application/json",
        error:function(XMLHttpRequest, textStatus, errorThrown){
          alert("ERROR " + textStatus+ " " + errorThrown);
        },
        success:function(data){
          for(var p in data){
            $("#data").append("<li>"+p+" : "+data[p]+"</li>");
          }
        },
        dataType:'json',
        processData: false
      });
    });
    </script>
  </head>
  <body>
    <h1>Graph</h1>
    <ul id="data"></ul>
  </body>
</html>


WEBブラウザのアドレスバーに直接
http://s3-ap-northeast-1.amazonaws.com/memorycraft/memocra.json
と入力するとロードできますが、別ホストで上記のようなHTMLページからのajaxではドメインが異なるためロードに失敗します。



これはchromeブラウザですが、通信をみるとOPTIONSメソッドで送信しており、以下のヘッダを送信していることがわかります。

  • Access-Control-Request-Headers
    • origin, content-type, accept
  • Access-Control-Request-Method
    • GET
  • Host
    • s3-ap-northeast-1.amazonaws.com
  • Origin
    • http://176.34.47.135


これがPreflightリクエストです。
このあとOrigin、Content-Type、AcceptヘッダをGETメソッドで送りますよと伝えています。
結果は403で返ってきました。

バケットのプロパティを見るとPermissionsタブに、「Edit CORS Configuration」というボタンがあるのでクリックすると以下のようなXMLの設定が表示されます。



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

CORSRuleにある設定がこのS3バケットで受け付けている設定になります。
これらは前述の
  • Access-Control-Allow-Origin
  • Access-Control-Allow-Methods
  • Access-Control-Max-Age
  • Access-Control-Allow-Headers

レスポンスヘッダに該当し、許可する条件を示しています。
このデフォルト設定をみると、アクセス元はどこでもよく、許可するHTTPでメソッドはGETのみ、認証キャッシュは3000秒で、Authorizationヘッダーのみを許可しています。

Authorizationヘッダーのみの許可なので、本リクエストで送る予定のAccept, Content-Type, Originヘッダーが許可されず403エラーとなっているようです。
ここで、AllowdHeaderを増やしてみます。

<?xml version="1.0" encoding="UTF-8"?>
<CORSConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
    <CORSRule>
        <AllowedOrigin>*</AllowedOrigin>
        <AllowedMethod>GET</AllowedMethod>
        <MaxAgeSeconds>3000</MaxAgeSeconds>
        <AllowedHeader>Authorization</AllowedHeader>
        <AllowedHeader>Accept</AllowedHeader>
        <AllowedHeader>Content-Type</AllowedHeader>
        <AllowedHeader>Origin</AllowedHeader>
</CORSRule>
</CORSConfiguration>

もしくはすべてのヘッダーを受け付けるようにワイルドカード*を指定します。

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

すると、GETであればアクセス可能になるので、ajaxリクエストは成功します。



この状態では、どのホストからでもGETリクエストであればアクセスできてしまうので、
接続するOriginを特定してみます。

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

ここで先ほどとはまた別のホストから同様にajaxでリクエストしてみると、Preflightリクエストで403が返り、アクセスできないことがわかります。



いままではS3のパーミッションはAWSアカウントを基準としたアクセス制御でしたが、
このようにHTTPレベルでもアクセス制御ができるようになりました。

これによって、S3の使用の仕方がとても広がるのではないかと思います。
以上です。