網頁

2014年5月10日 星期六

[野人獻曝] 將Cloudfront的資源加上防外連機制

最近因為 Cloudfront 的流量費用越來越可怕了,
所以牙一咬就決定擋掉圖片的所有外連圖個減少開銷。

Cloudfront 正好有提供 Signed URL 這個玩意可以達成我的要求。

這玩意簡單來說就是在所要求的資源(如:圖片、檔案等)的網址後面多加一串 Query String。
例如:http://www.example.com/a.txt經過改造後會變成http://www.example.com/a.txt?blahblah
而Server只要認那串 Query String 就可以決定是否要出所要求的內容,
就可以控制不必要的外連。

那接著就不提廢話,直接實作吧!

第一步要先到 AWS 的 Security Credentials
然後頁面往下拉找到一個 Key Pairs 的 tab,
接著點擊 Create a new key pair
他會產生一組pem檔並且要求下載,
請把這個檔案下載儲存。
同時頁面上也會顯示一組 Axxxxxxxxxx 的 key ,
請務必把這組字串複製下來。

第二步要寫出一個產生 Signed URL 的 function,
內容大概如下

<?php
/**
  * $resource:要處理的檔案網址  例如http://www.example.com/a.txt
  * $expire:過期的時間,單位是秒,這裡我預設有效時間為一個小時
*/
function create_signed_url ($resource, $expire = 3600)
{
    $aws_key = 'Axxxxxxxx';  // 就是上述所提到的key
    $pem_file = '';   // 剛才下載的pem檔案放置路徑
    $expire_time = time() + $expire;  // 這裡的值是個 Unix timestamp
    // 以下是 Cloudfront 的存取策略,內容基本上是個json string
    // 但是千萬不要用json_encode這個函式去編出內容,因為格式會不一樣
    // 詳細可以參考:
    // 以下是指特定的檔案(即$resource)在$expire_time之後即無法取得內容
    $policy = '{
        "Statement":[{
            "Resource":"' . $resource . '",
            "Condition":{
                "DateLessThan":{
                    "AWS:EpochTime":'. $expire_time . '
                }
            }
        }]
    }';
    // 接下來要利用openssl的相關函式產生所需的簽章內容
    $private_key_content = file_get_contents($pem_file);

    // 先把內容丟到openssl_get_privatekey算出私鑰
    $pkey = openssl_get_privatekey($private_key_content);
    // 接著呼叫openssl_sign取得簽章內容
    // $policy是上面設定的讀取策略,產生的結果會放在$signature中
    openssl_sign($policy, $signature, $pkey);
    // 接著釋放這組key內容
    openssl_free_key($pkey);

    // 然後要真正產生簽章過的URL
    // 請注意:Policy和Signature的內容要用base64_enocde編碼外,還要把特定的三個符號替換掉    // 否則產生的url也會無法取得資源
    return $resource."?".http_build_query(array(
        'Policy'            =>  str_replace(
            array('+', '=', '/'),
            array('-', '_', '~'),
            base64_encode($policy)
        ),
        'Key-Pair-Id'       =>  $aws_key,
        'Signature'         =>  str_replace(
            array('+', '=', '/'),
            array('-', '_', '~'),
            base64_encode($signature)
        ),
    ));

}
?>

然後在網頁上用到該資源的連結/圖片全部用這個function處理後,
輸出的url應該就會有一長串的Query String了。

但是這樣作還不夠,
我們還要進去 Cloudfront 的控制台設定哪些項目需要被限制存取。
所以進入控制台後請選擇目標項目後點擊 Distribution Settings
接著選擇 Behaviors
如果沒意外的話有一組 Path Pattern 為 Default(*) 的項目,
請點擊他後按下Edit。
出現編輯畫面後請往下拉,
找到 Restrict Viewer Access (Use Signed URLs) 並選擇 Yes 後儲存。
這樣這個項目內所有資源就會被限制存取。
(當然也可以設定什麼樣的檔案才要被限制,這個設定可以寫在 Path Pattern 中)

上面的動作做完後,等 Cloudfront 佈署完成後應該就會生效了。
你應該會發現沒加一長串簽章的網址全部都出現 Access Denied 或是其他之類的錯誤訊息,
而有一長串簽章的網址都應該是可以正確讀取,
這樣表示就一切正常了。