Android/iOSから署名付きcookieでCloudFrontにアクセスする


アプリからCloudFrontにアクセスする際に署名付きCookieを使用したかったので試してみた
サーバ側でCloudFrontから署名付きcookieを取得し
「CloudFront-Policy」「CloudFront-Signature」「CloudFront-Key-Pair-Id」
をアプリ側に渡している状態を前提としている

# 画像の場合
## サンプルコード (Android)
画像リクエストにCookieを設定する画像ライブラリとしてFrescoとGlideがあったが、Glideの方がささっと実装できそうだったのでGlideを選択した

外部アクセスするのでマニフェストファイルに以下パーミッションを追加

```
<uses-permission android:name="android.permission.INTERNET" />
```

- MainActivity.java

```java
package com.example.makotokawano.imagetest;

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.widget.ImageView;
import com.bumptech.glide.Glide;
import com.bumptech.glide.load.model.GlideUrl;
import com.bumptech.glide.load.model.Headers;
import com.bumptech.glide.load.model.LazyHeaders;

public class MainActivity extends AppCompatActivity {

  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    ImageView matchImage = findViewById(R.id.match_image);
    String url = "[CloudFrontコンテンツURL]";

    //cookieに「CloudFront-Policy」「CloudFront-Signature」「CloudFront-Key-Pair-Id」の
    //３パラメーターが必要
    String cookie =
        "CloudFront-Policy=" + "XXXX" + ";"
        + "CloudFront-Signature=" + "XXXX" + ";"
        + "CloudFront-Key-Pair-Id=" + "XXXX" + ";";

    Headers headers = new LazyHeaders.Builder()
        .addHeader("Cookie", cookie)
        .build();

    Glide.with(this).load(new GlideUrl(url, headers)).into(matchImage);
  }
}
```

- activity_main.xml

```xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@color/colorPrimaryDark"
    tools:context=".MainActivity"
    >

    <ImageView
        android:id="@+id/match_image"
        android:layout_width="200dp"
        android:layout_height="200dp" />

</LinearLayout>
```

## サンプルコード（Swift）

UIImageViewをカスタマイズする
（参考： https://qiita.com/nbapps_dev/items/d22837b04d8bb0a3127d ）

```swift
import Foundation
import UIKit
class SimpleAsyncImageView: UIImageView {    
    //画像を非同期で読み込む
    func loadImage(urlString: String){
        
        var req = URLRequest(url: NSURL(string:urlString)! as URL)

        //cookieをセット
        let cookieValue = 
            "CloudFront-Policy=" + "XXXX" + ";"
            + "CloudFront-Signature=" + "XXXX" + ";"
            + "CloudFront-Key-Pair-Id=" + "XXXX" + ";"

        req.setValue(cookieValue, forHTTPHeaderField: "Cookie")

        let conf =  URLSessionConfiguration.default;
        let session = URLSession(configuration: conf, delegate: nil, delegateQueue: OperationQueue.main);
        
        session.dataTask(with: req, completionHandler:
            { (data, resp, err) in
                if((err) == nil){ //Success
                    let image = UIImage(data:data!)
                    self.image = image;
                    
                }else{ //Error
                    print("AsyncImageView:Error \(err?.localizedDescription)");
                }
        }).resume();
    }
}

```

# 動画の場合

## サンプルコード (Android)

- MainActivity.java

```java
package com.example.makotokawano.imagetest;

import android.net.Uri;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.widget.ImageView;
import android.widget.MediaController;
import android.widget.VideoView;
import com.bumptech.glide.Glide;
import com.bumptech.glide.load.model.GlideUrl;
import com.bumptech.glide.load.model.Headers;
import com.bumptech.glide.load.model.LazyHeaders;
import java.util.HashMap;
import java.util.Map;

public class MainActivity extends AppCompatActivity {

  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    //cookieに「CloudFront-Policy」「CloudFront-Signature」「CloudFront-Key-Pair-Id」の
    //３パラメーターが必要
    String cookie =
        "CloudFront-Policy=" + "XXXX" + ";"
        + "CloudFront-Signature=" + "XXXX" + ";"
        + "CloudFront-Key-Pair-Id=" + "XXXX" + ";";

    VideoView videoView = findViewById(R.id.video_view);
    Map<String, String> videoHeaders = new HashMap<String, String>();
    videoHeaders.put("Cookie", cookie);
    videoView.setVideoURI(Uri.parse("[CloudFrontコンテンツURL]"), videoHeaders);
    videoView.setMediaController(new MediaController(this));

    videoView.start();
  }
}
```

- acitivity_main.xml

```xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@color/colorPrimaryDark"
    tools:context=".MainActivity"
    android:orientation="vertical"
    >

    <VideoView
        android:id="@+id/video_view"
        android:layout_width="200dp"
        android:layout_height="200dp"
        />
</LinearLayout>
```

## サンプルコード (Swift)

[この辺](https://stackoverflow.com/questions/48899128/how-to-play-video-with-cookies-content-in-ios-using-avplayer-in-swift)の記事を参考にした

- ViewController.swift

```swift
import UIKit
import AVKit
import AVFoundation
import CoreMedia

class ViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()

        let button = UIButton(type: .system)
        button.setTitle("Play", for: .normal)
        button.addTarget(self, action: #selector(self.onClick(_:)), for: .touchUpInside)
        
        button.sizeToFit()
        button.center = self.view.center
        self.view.addSubview(button)
    }
    
    @objc func onClick(_ sender: AnyObject) {
        // Do any additional setup after loading the view, typically from a nib.
        
        let url = URL(string: "[CloudFrontコンテンツURL]")
        let cookieStr1 = "CloudFront-Policy=" + "XXXX"
        let cookieStr2 = "CloudFront-Signature=" + "XXXX"
        let cookieStr3 = "CloudFront-Key-Pair-Id=" + "XXXX"

        let cookieHeaderField1 = ["Set-Cookie": cookieStr1]
        let cookies1 = HTTPCookie.cookies(withResponseHeaderFields: cookieHeaderField1, for: url!)
        let cookieHeaderField2 = ["Set-Cookie": cookieStr2]
        let cookies2 = HTTPCookie.cookies(withResponseHeaderFields: cookieHeaderField2, for: url!)
        let cookieHeaderField3 = ["Set-Cookie": cookieStr3]
        let cookies3 = HTTPCookie.cookies(withResponseHeaderFields: cookieHeaderField3, for: url!)
        HTTPCookieStorage.shared.setCookies(cookies1, for: url, mainDocumentURL: url)
        HTTPCookieStorage.shared.setCookies(cookies2, for: url, mainDocumentURL: url)
        HTTPCookieStorage.shared.setCookies(cookies3, for: url, mainDocumentURL: url)
        
        let cookiesArray = HTTPCookieStorage.shared.cookies!
        let values = HTTPCookie.requestHeaderFields(with: cookiesArray)
        let cookieArrayOptions = ["AVURLAssetHTTPHeaderFieldsKey": values]
        let assets = AVURLAsset(url: url!, options: cookieArrayOptions)
        let item = AVPlayerItem(asset: assets)
        let player = AVPlayer(playerItem: item)
        
        // 動画プレイヤーの用意
        let playerController = AVPlayerViewController()
        playerController.player = player
        self.present(playerController, animated: true, completion: {
            player.play()
        })
    }
    
    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }


}

```

SetCookiesの部分が冗長的な書き方なのでもうちょっとなんとかしたい