java amazonS3 amazon s3

Amazon S3 PreSigned Url

詹國忠 2017/11/30 11:00:00
2810

Amazon S3 PreSigned Url


簡介

可預先產生已簽名並限定時間的URL供使用者上傳或是下載檔案,使得真正的AWS S3位置可以確保隱藏並加以控管。

作者

詹國忠


1.前言

  • 為了節約成本,可將靜態資源存儲在AWS S3上。但S3資源默認是私有的,只有Owner登錄後才能訪問,如果將資源改為Public的,這又不安全,那怎麼辦呢?可以使用AWS SDK生成預簽名的URL,限定用戶在有限的時間內可訪問。
  • 開發工具使用Eclipse。

2.目的

  • 可預先產生已簽名並限定時間的URL供使用者上傳或是下載檔案,使得真正的AWS S3位置可以確保隱藏並加以控管。

3.開始前準備

  • 本架構建立於以下版本的環境:
  • JDK6以上
  • Eclipse Mars.2 Release (4.5.2)
  • Maven
  • AWS SDK for JAVA

4.下傳檔案

4.1使用以下程式碼產生URL

import java.io.IOException;

import java.net.URL;

 

import com.amazonaws.AmazonClientException;

import com.amazonaws.AmazonServiceException;

import com.amazonaws.HttpMethod;

import com.amazonaws.auth.profile.ProfileCredentialsProvider;

import com.amazonaws.services.s3.AmazonS3;

import com.amazonaws.services.s3.AmazonS3Client;

import com.amazonaws.services.s3.model.GeneratePresignedUrlRequest;

 

public class GeneratePreSignedUrl {

        private static String bucketName = "*** Provide a bucket name ***";

      private static String objectKey  =  "*** Provide an object key ***";

 

        public static void main(String[] args) throws IOException {

                AmazonS3 s3client = new AmazonS3Client(new ProfileCredentialsProvider());

 

                try {

                        System.out.println("Generating pre-signed URL.");

                        java.util.Date expiration = new java.util.Date();

                        long milliSeconds = expiration.getTime();

                   milliSeconds += 1000 * 60 * 60; // Add 1 hour.

                   expiration.setTime(milliSeconds);

 

                        GeneratePresignedUrlRequest generatePresignedUrlRequest =

                                    new GeneratePresignedUrlRequest(bucketName, objectKey);

                        generatePresignedUrlRequest.setMethod(HttpMethod.GET);

                        generatePresignedUrlRequest.setExpiration(expiration);

 

                        URL url = s3client.generatePresignedUrl(generatePresignedUrlRequest);

 

                        System.out.println("Pre-Signed URL = " + url.toString());

                } catch (AmazonServiceException exception) {

                        System.out.println("Caught an AmazonServiceException, " +

                                        "which means your request made it " +

                                        "to Amazon S3, but was rejected with an error response " +

                        "for some reason.");

                        System.out.println("Error Message: " + exception.getMessage());

                        System.out.println("HTTP  Code: "    + exception.getStatusCode());

                        System.out.println("AWS Error Code:" + exception.getErrorCode());

                        System.out.println("Error Type:    " + exception.getErrorType());

                        System.out.println("Request ID:    " + exception.getRequestId());

                } catch (AmazonClientException ace) {

                        System.out.println("Caught an AmazonClientException, " +

                                        "which means the client encountered " +

                                        "an internal error while trying to communicate" +

                                        " with S3, " +

                        "such as not being able to access the network.");

                        System.out.println("Error Message: " + ace.getMessage());

                }

        }

}

4.2程式碼內容

  1. 以上述程式碼可產生出已簽名的URL,使私有的S3位置檔案提供下載
  2. 紅色字體部分為設定此URL的有效時間,以此程式碼為例,產出的URL在一個小時後就會逾期無法再使用
  3. 藍色字體部分是自己私有的S3bucket nameObject key
  4. 產生的URL範例: https:// example-bucket.s3.amazonaws.com/sample.mp4?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Date=20170630T080349Z&X-Amz-SignedHeaders=host&X-Amz-Expires=3600&X-Amz-Credential=AKIAJUC5OYTA4E5EPWRA%2F20170630%2Fap-southeast-1%2Fs3%2Faws4_request&X-Amz-Signature=69ef7fe08eb90e4f6c73bbada7379970a63fff53fa0502a1b364d67e58e273a4

 

5.上傳檔案

5.1使用以下程式碼產生URL

import java.io.IOException;

import java.net.URL;

 

import com.amazonaws.AmazonClientException;

import com.amazonaws.AmazonServiceException;

import com.amazonaws.HttpMethod;

import com.amazonaws.auth.profile.ProfileCredentialsProvider;

import com.amazonaws.services.s3.AmazonS3;

import com.amazonaws.services.s3.AmazonS3Client;

import com.amazonaws.services.s3.model.GeneratePresignedUrlRequest;

 

public class GeneratePreSignedUrl {

        private static String bucketName = "*** Provide a bucket name ***";

        private static String objectKey  =  "*** Provide an object key ***";

 

        public static void main(String[] args) throws IOException {

                AmazonS3 s3client = new AmazonS3Client(new ProfileCredentialsProvider());

 

                try {

                        System.out.println("Generating pre-signed URL.");

                        java.util.Date expiration = new java.util.Date();

                        long milliSeconds = expiration.getTime();

                        milliSeconds += 1000 * 60 * 60; // Add 1 hour.

                        expiration.setTime(milliSeconds);

 

                        GeneratePresignedUrlRequest generatePresignedUrlRequest =

                                    new GeneratePresignedUrlRequest(bucketName, objectKey);

                        generatePresignedUrlRequest.setMethod(HttpMethod.GET);

                        generatePresignedUrlRequest.setExpiration(expiration);

 

                        URL url = s3client.generatePresignedUrl(generatePresignedUrlRequest);

 

                        System.out.println("Pre-Signed URL = " + url.toString());

                } catch (AmazonServiceException exception) {

                        System.out.println("Caught an AmazonServiceException, " +

                                        "which means your request made it " +

                                        "to Amazon S3, but was rejected with an error response " +

                        "for some reason.");

                        System.out.println("Error Message: " + exception.getMessage());

                        System.out.println("HTTP  Code: "    + exception.getStatusCode());

                        System.out.println("AWS Error Code:" + exception.getErrorCode());

                        System.out.println("Error Type:    " + exception.getErrorType());

                        System.out.println("Request ID:    " + exception.getRequestId());

                } catch (AmazonClientException ace) {

                        System.out.println("Caught an AmazonClientException, " +

                                        "which means the client encountered " +

                                        "an internal error while trying to communicate" +

                                        " with S3, " +

                        "such as not being able to access the network.");

                        System.out.println("Error Message: " + ace.getMessage());

                }

        }

}

 

可以看到產生URL的方式與下載檔案的時候一樣,差別在於之後的上傳檔案的使用方式

5.2使用java方式上傳

l   程式碼

   public static void UploadObject(URL url, String content) throws IOException {

                HttpURLConnection connection = (HttpURLConnection) url.openConnection();

                connection.setDoOutput(true);

                connection.setRequestMethod("PUT");

                OutputStreamWriter out = new

OutputStreamWriter(connection.getOutputStream());

                out.write(content);

                out.close();

                int responseCode = connection.getResponseCode();

                System.out.println("Service returned response code " + responseCode);

 

        }

l   由以上程式碼可以看到,只需直接使用產生好的URL即可上傳,不會暴露出S3檔案的bucket nameObject key

5.3使用javascript方式上傳

l   程式碼

        <script type="text/javascript">

                var s3presignedUrl = '<%=preSignedUrl%>';

                s3presignedUrl = decodeURIComponent(s3presignedUrl);

                var fileChooser = document.getElementById('file-chooser');

                var button = document.getElementById('upload-button');

                button.addEventListener('click', function() {

                        var file = fileChooser.files[0];

                        $.ajax({

                url : s3presignedUrl,

                type : "PUT",

                data : file,

                dataType : "text",

                cache : false,

                contentType : file.type,

                processData : false,

                xhr: function() {

                    var myXhr = $.ajaxSettings.xhr();

                    if(myXhr.upload){

                        myXhr.upload.addEventListener('progress',progress, false);

                    }

                    return myXhr;

                  }

            })

            .done(function(){

                    console.log("upload completed...." + new Date());

                    alert("done");

                console.info('YEAH', s3presignedUrl.split('?')[0].substr(6));

            })

            .fail(function(){

                    alert("fail");

                console.error('damn...');

            });

                }, false);

 

                function progress(e){

                    if(e.lengthComputable){

                        var max = e.total;

                        var current = e.loaded;

                        var Percentage = (current * 100)/max;

                        console.log(Percentage);

                        if(Percentage >= 100) {

                           // process completed 

                        }

                    } 

                 }

 

</script>

l   以上的程式碼範例即可使用由後端產生的已簽名的URL在前端頁面使用ajax的方式上傳檔案

l   紅色字體的部分為此ajax functioncallback method,由此callback可得知目前檔案上傳的進度並以百分比顯示,若另外再搭配jquery的進度條plugin,就可以在前端顯示各式風格的上傳進度條

6.優點

l  安全,如上所述,不會暴露出S3檔案的bucket nameObject key,並且在產生PresighedUrl時可以設定過期時間,大大的提升了檔案的安全及機密性

l  節省資源,以一般的上傳來說,上傳一次就會佔用兩倍的資源( client à server,然後從 server à AWS )。所以,如果經常上傳大量的數據,那麼就會浪費大量的資源。在這種情況下,將上傳方式改為使用PresighedUrl,直接將數據推送到S3所展現出來的成效將會是非常的明顯。

詹國忠