エンジニアレポート

CloudFront+S3+Video.js+ 署名付きCookieでクローズドな動画配信

2019年01月31日

データスタジアムエンジニアの片岡です。

主にサッカーに関するサービスの開発を担当しています。スポーツ界では近年データと動画を絡めたサービスへの需要が多く、私自身も動画システムの開発に多く携わっています。

今回、AWSを使ってシステムの利用ユーザーに限定して公開する、クローズドな映像配信の設定で試行錯誤したので記事に残します。

全体図

まずは全体図です。

zentai

S3に再生したい動画(HLS形式。ライブでもVODでも)があり、それをCloud Frontで配信するのですが、全世界に公開するのではなく、認証されたユーザーにのみ公開するようにします。

Cloud Frontには署名付きURL署名付きCookieという2種類のアクセス制御方式があります。HLS形式で署名付きURLを採用する場合、m3u8内の各tsにも署名パラメータを付与する必要があり面倒なため、Cookieを使う方法を採用します。

Cookieを送信するためには、Cookieを発行するサーバのドメインとCloud Frontを同ドメイン(***.mydomain.com)にする必要があるので、CNAMEを設定します。またACM(Amazon Certificate Manager)を使用してhttps化を行います。

アプリケーション側をaaa.mydomain.com、Cloud Front側をbbb.mydomain.comとします。

以下の手順で設定していきます。

S3バケット作成・ファイル準備・CORS設定

まずはバケットを作成し、hlsフォルダを作成してその中にHLSファイル(m3u8とts)をアップロードします。

次にAWSコンソール上でバケットを開いた状態でアクセス権限タブのCORS設定で、以下を入力して保存します。
http://aaa.mydomain.com:8000はローカルPCでの開発用です。

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

バケットポリシーは後で設定します。

Cloud Front

Distribution作成

Create Distributionを押して、Distribution作成に入ります。

  • Origin Domain Nameで先ほど作成したS3バケットを選択
  • Restrict Bucket AccessをYesにする
  • Cache Based on Selected Request HeadersでWhitelistを選択し、下のWhitelist HeadersでOriginをAdd
  • Restrict Viewer AccessをYesにする (これで署名なしのアクセスは拒否される)

他はデフォルトのままで右下のCreate Distributionを押してDistributionを作成します。

Origin Access IdentityをS3バケットポリシーに設定

Cloud Frontの左メニューのOrigin Access Identityに行き、Distribution作成時に作成されたOrigin Access IdentityのIDをコピーします。
S3バケットの目的のバケットを開き、アクセス権限のバケットポリシーで以下を入力して保存します。
{}の部分は書き換えてください。

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "Grant a CloudFront Origin Identity access",
            "Effect": "Allow",
            "Principal": {
                "AWS": "arn:aws:iam::cloudfront:user/CloudFront Origin Access Identity {↑でコピーしたOrigin Access IdentityのID}"
            },
            "Action": "s3:GetObject",
            "Resource": "arn:aws:s3:::{バケット名}/*"
        }
    ]
}
DNS CNAME設定

Cloud Frontに割り当てるCNAMEをお名前.comやRoute53で設定します。
作成したDistributionのDomain Name(d**********.cloudfront.net)を以下のように設定します。
bbb.mydomain.com. IN CNAME d123456789.cloudfront.net.

ACM証明書取得
  1. AWSコンソールでCloud Frontの対象Distributionを開き、GeneralタブでEditを押します。
  2. Request or Import a Certificate with ACMを押すと、証明書のリクエスト画面が開きます。この時、右上のリージョン表示がバージニア北部になっていることを確認します。(そうでないとCloud Frontでは使えない)
  3. ドメイン名にbbb.mydomain.comを入力して次へボタンを押します。
  4. 検証方法の選択画面でDNSの検証を選択して確認ボタンを押し、さらに確定とリクエストボタンを押します。
  5. 次の画面でドメインの左側にある▶をクリックするとDNS検証のためにmydomain.comのCNAMEに設定すべき内容が表示されます。これをお名前.com等で設定し、しばらく待つと、ACMの画面で証明書の状況欄が発行済みとなります。

あくまでAWS側がドメイン所有権を確認する検証用なので、発行済になった後はCNAMEは削除してOKです。

Cloud FrontのCNAME設定
  1. 証明書が発行済みになった後、AWSコンソールでCloud FrontのDistributionを開き、GeneralタブでEditを押します。
  2. Alternate Domain Names(CNAMEs)で、bbb.mydomain.comを入力し、下のSSL CertificateでCustom SSL Certificateを選択して右下のYes, Editボタンを押して保存します。
CloudFrontキーペア作成
  1. プログラムからCloud Frontの署名をするためには、専用キーペアの作成が必要です。
  2. AWSコンソールにrootアカウントでログインし、右上のユーザー名をクリック、セキュリティ認証情報を押します。(rootでないとできません)
    ここで警告が出ますが×で閉じます。
  3. Cloud Frontのキーペアの中の新しいキーペアの作成を押すとキーペアが作成され、プライベートキーファイルのダウンロードボタンを押してキーをダウンロードします。

ここまででAWS側の設定は完了です!

アプリケーション作成

次にアプリケーションの作成に入ります。PHP7 + Laravel5.7を使いますが、他の言語・フレームワークでも考え方は同じです。
composerでLaravelアプリケーションを作成した後からの説明になります。

AWS SDKインストール

プロジェクトルートで、composer require aws/aws-sdk-phpを実行してAWS SDKをインストール(ダウンロード)します。

Cloud Front用プライベートキーファイルの配置

プロジェクトルートのstorage/appに先ほどダウンロードしたプライベートキーファイルを置きます。

Controller作成

ブラウザからアクセスする画面用のControllerを作成します。
※SPAで作成したい場合はAPIとして作成してください。

ここでは認証チェックや、動画を再生する権利があるかどうかのシステム固有チェックをした後、Cloud Frontの署名付きCookieを作成します。
Cookieは以下3つを作成します。

  • CloudFront-Policy
  • CloudFront-Signature
  • CloudFront-Key-Pair-ID

CookieのDomainを、mydomain.comにするのがポイントです。

app/Http/Controllers/PlayVideoController.php

<?php

namespace App\Http\Controllers;

use Aws\CloudFront\CloudFrontClient;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Cookie;
use Illuminate\Support\Facades\Log;

class PlayVideoController extends Controller
{
    public function show(Request $request)
    {
        // 事前に認証・権限のチェック

        $cloudFront = new CloudFrontClient([
            'region'  => 'us-west-2',
            'version' => '2014-11-06'
        ]);

        // CloudFrontの対象リソース(CNAMEにより自分のドメイン)
        $resourceKey = "https://bbb.mydomain.com/*";
        // 有効期限
        $expires = time() + 3600;

        $customPolicy = <<getSignedCookie([
            'policy' => $customPolicy,
            'private_key' => storage_path() . '/app/{プライベートキーファイル名}',
            'key_pair_id' => '{キーペアのアクセスキーID}'
        ]);

        // Cookie設定
        $minutes = 5;
        $path = '/';
        $domain = 'mydomain.com'; // こうすることでaaa.mydomain.comで発行したCookieがbbb.mydomain.comにも送信される
        $secure = env('APP_ENV') == 'production'; // ローカル開発時にhttpを使用する場合はfalse、本番環境でhttpsを使用する場合はtrue
        $httponly = false; // Video.jsでjsからCookie送信するためfalse
        Cookie::queue(Cookie::make('CloudFront-Policy', $cookie['CloudFront-Policy'],
            $minutes, $path, $domain, $secure, $httponly));
        Cookie::queue(Cookie::make('CloudFront-Signature', $cookie['CloudFront-Signature'],
            $minutes, $path, $domain, $secure, $httponly));
        Cookie::queue(Cookie::make('CloudFront-Key-Pair-Id', $cookie['CloudFront-Key-Pair-Id'],
            $minutes, $path, $domain, $secure, $httponly));

        return view('play_video')->with(
            ['signedCookie' => $cookie]
        );
    }
}
routesを設定
routes/web.php

Route::get('/play_video', 'PlayVideoController@show');
LaravelのCookie暗号化を解除

LaravelはデフォルトでCookieを暗号化しますが、今回作成するCookieはCloud Frontで受け取るものなので、暗号化しないようにする必要があります。

app/Http/Middleware/EncryptCookies.php

    protected $except = [
        'CloudFront-Policy',
        'CloudFront-Signature',
        'CloudFront-Key-Pair-Id'
    ];
blade作成 (htmlテンプレート) ※Video.js使用
resources/views/play_video.blade.php

<!doctype html>
<html>
<head>
    <link href="//vjs.zencdn.net/7.3.0/video-js.min.css" rel="stylesheet">
</head>
<body>
<h1>Cloud Front Signed Cookie Sample</h1>
<video id="video" class="video-js vjs-default-skin vjs-big-play-centered" controls preload="auto"
       width="320" height="240">
</video>
<script src="//vjs.zencdn.net/7.3.0/video.min.js"></script>
<script src="https://code.jquery.com/jquery-3.3.1.js"></script>
<script>
    $(document).ready(function() {
        var player = videojs('video');
        player.src({
            src: 'https://bbb.mydomain.com/hls/index.m3u8',
            type: 'application/x-mpegURL',
            withCredentials: true
        });
    });
</script>
</body>
</html>

withCredentials: trueとすることで、クロスドメインでCookieを送信することができます。(aaa.mydomain.comで開いたページからjavascriptでbbb.mydomain.comにCookie送信)

動作確認

ローカルPCで確認

ローカルPC上でアプリケーションを動作させて確認する際はhostsに以下を記載しておきます。(macの場合は/etc/hosts)

127.0.0.1 aaa.mydomain.com

php artisan serveを実行してLaravelのWebサーバを起動し、ブラウザでhttp://aaa.mydomain.com:8000/play_videoにアクセスします。

動画が再生されれば成功です!

AWSで確認

EC2にアプリを構築し、ALBを設定します。ALBにはCNAMEでaaa.mydomain.comを割り当てます。後はローカルと考え方は同じです。(hostsから先程の設定を削除して確認)

直接再生不可の確認

https://bbb.mydomain.com/hls/index.m3u8を直接開いてもエラーになり再生はできません。(mac Safari、 Windows Edge、 VLC Playerで確認します。ChromeやIEでは元々直接再生には対応していません。)

エンジニア募集中!

データスタジアムでは一緒に働いていただけるエンジニアを募集しています。野球、サッカー、バスケなどスポーツが好きな方であれば、とても面白い仕事ができる会社です。興味を持たれた方はぜひこちらをご覧ください。

エンジニアトップ エンジニアインタビュー エンジニアレポート

  • 採用情報
  • おしらせ
  • 掲載事例

ページトップヘ