酢ろぐ!

カレーが嫌いなスマートフォンアプリプログラマのブログ。

AWS SDK for .NETを使わずにクエリ文字列認証でAmazon S3から画像ファイルをダウンロードする

昨日書いた記事「AWS SDK for .NETを使ってAmazon S3からファイルをダウンロードしてImageコントロールに表示する - 酢ろぐ!」では、AWS SDK for .NETを使ってAmazon S3からファイルをダウンロードする方法を書きました。

特に触れていませんでしたがAmazon S3ではファイルごとにアクセス権限を設定することができます。静的サイトの場合はPublic、またはURL直リンクでファイルに参照されたくない場合はPrivateなどと言ったような感じです(実際にはもっと細かな設定が可能です)。

Privateに設定されているファイルにアクセスするための方法はいくつかあるのですが、AWS SDK for .NETを導入するまでもないような場合には、僕はAmazon S3のファイルに参照する際にクエリ文字列認証と呼んでいる「Authenticating REST Requests」をよく使います。

クエリ文字列認証の方法に関してはやり方が下記の通り書かれています。

前準備 - C#でUnixエポックを求めるための準備

Expires(期限切れ日時)を求めるためにUnixエポックを使用する必要があります。

UNIX時間(POSIX time)を求めるためにDateTimeクラスの拡張メソッドを定義しておきます。大昔に書いた「C#でUNIX時刻を取得する - 酢ろぐ!」から参考にしましょう。

public static class DateTimeExtensions
{
    public static double ToIntervalSince1970(this DateTime date)
    {
        // 1970/1/1のDateTimeオブジェクトを作成する
        var baseDate = new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc);
        // 1970/1/1からの差分を求める
        var span = date.ToUniversalTime() - baseDate;
        return span.TotalSeconds;
    }
}

クエリ文字列を生成する

認証文字列(Access Signatures)付きのURLを生成するためのコードを書いてみました。

public static string CreateS3Url(string path, string bucket,
    string accessKey, string accessSecretKey, 
    DateTime expireDate, string host, string protocol)
{
    // URLの頭の部分を先に作成しておく
    var url = string.Format("{0}://{1}.{2}/{3}", protocol, bucket, host, path);

    // 期限切れとなる時間をUNIX時刻で取得する
    var expireTime = (int)expireDate.ToIntervalSince1970();
    var expires = expireTime.ToString();

    // 以下のフォーマットで認証文字列を作成する
    //
    //  StringToSign =  HTTP-Verb + "\n" +
    //                      Content-MD5 + "\n" +
    //                      Content-Type + "\n" +
    //                      Date + "\n" +
    //                      CanonicalizedAmzHeaders +
    //                      CanonicalizedResource;
    //
    //  ※CanonicalizedResourceは「/backetName/path」の形にすること
    var signature = string.Format("GET\n\n\n{0}\n/{1}/{2}", expires, bucket, path);

    var bytes = Encoding.UTF8.GetBytes(signature);
    var sha1 = new System.Security.Cryptography.HMACSHA1(Encoding.UTF8.GetBytes(accessSecretKey));
    var hashed = sha1.ComputeHash(bytes);

    signature = Uri.EscapeDataString(Convert.ToBase64String(hashed));

    url = string.Format("{0}?AWSAccessKeyId={1}&Expires={2}&Signature={3}", url, accessKey, expires, signature);

    return url;
}

上記のCreateS3Urlメソッドを使うことで、クエリ文字列認証付きのURLを生成することが可能となりました。ボタンをクリックするとURLを生成して、Imageコントロールに表示します。

private void Button_Click(object sender, RoutedEventArgs e)
{
    var accessKey = "アクセスキー";
    var accessSecretKey = "アクセスシークレット";

    var photoUrl = CreateS3Url("directory/temp.jpg", "data2.softbuild.jp",
        accessKey, accessSecretKey,
        DateTime.Now.Subtract(TimeSpan.FromDays(-1)),
        "s3-ap-northeast-1.amazonaws.com", "http");

    var imageSource = new BitmapImage();
    imageSource.BeginInit();
    imageSource.UriSource = new Uri(photoUrl);
    imageSource.EndInit();

    DownloadedImage.Source = imageSource;
}

これをWPFで実装したものを用意しました。実行すると下図のようになります。

f:id:ch3cooh393:20140402032728p:plain