在自己架設的伺服器中,如果要把檔案上傳到AWS S3,一種常見的做法是先將檔案上傳到自身伺服器然後再上傳到AWS S3,但是這樣太耗費資源了。 幸好AWS S3有方式可以讓你在前端直接將檔案上傳到S3中,以下說明如何設定前端的HTML,讓檔案直接上傳。
首先你要有可以存取Bucket
的 AWSAccessKeyId
與 AWSSecretAccessKey
以下資料皆來自https://docs.aws.amazon.com/AmazonS3/latest/API/sigv4-post-example.html
AWSAccessKeyId
AKIAIOSFODNN7EXAMPLE
AWSSecretAccessKey
wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY
Bucket
sigv4examplebucket
以下是HTML Form的例子
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 <html > <head > <meta http-equiv ="Content-Type" content ="text/html; charset=UTF-8" /> </head > <body > <form action ="http://sigv4examplebucket.s3.amazonaws.com/" method ="post" enctype ="multipart/form-data" > Key to upload: <input type ="input" name ="key" value ="user/user1/myfile.csv" /> <br /> <input type ="hidden" name ="acl" value ="public-read" /> <input type ="hidden" name ="success_action_redirect" value ="http://localhost:8080/uploaded.html" /> Content-Type: <input type ="hidden" name ="x-amz-meta-uuid" value ="14365123651274" /> <input type ="hidden" name ="x-amz-server-side-encryption" value ="AES256" /> <input type ="text" name ="X-Amz-Credential" value ="AKIAIOSFODNN7EXAMPLE/20151229/us-east-1/s3/aws4_request" /> <input type ="text" name ="X-Amz-Algorithm" value ="AWS4-HMAC-SHA256" /> <input type ="text" name ="X-Amz-Date" value ="20151229T000000Z" /> Tags for File: <input type ="hidden" name ="Policy" value ='<Base64-encoded policy string>' /> <input type ="hidden" name ="X-Amz-Signature" value ="<Signature-value>" /> File: <input type ="file" name ="file" /> <br /> <input type ="submit" name ="submit" value ="Upload to Amazon S3" /> </form > </html >
Base64-encoded policy string Policy 應由後端產生,他是一個JSON物件,以下是一個例子
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 { "expiration" : "2015-12-30T12:00:00.000Z" , "conditions" : [ { "bucket" : "sigv4examplebucket" } , [ "starts-with" , "$key" , "user/user1/" ] , { "acl" : "public-read" } , { "success_action_redirect" : "http://localhost:8080/uploaded.html" } , { "x-amz-meta-uuid" : "14365123651274" } , { "x-amz-server-side-encryption" : "AES256" } , { "x-amz-credential" : "AKIAIOSFODNN7EXAMPLE/20151229/us-east-1/s3/aws4_request" } , { "x-amz-algorithm" : "AWS4-HMAC-SHA256" } , { "x-amz-date" : "20151229T000000Z" } ] }
expiration
表示這個上傳的Request可用的期限
conditions
設定條件,在檔案上傳前AWS S3會檢查一下,是否和HTML Form中的相符合
bucket
Bucket 名稱,此範例為 sigv4examplebucket
“starts-with”, “$key”
要放在這個Bucket的 Prefix,此範例為user/user1/
,在HTML Form中的 key 需要符合在此設定的前綴祠才可以,如 user/user1/myfile.csv
acl
設定 S3 ACL, 如設為:public-read 表示可以被公開讀取,也可以設為 private ,表示是私有的
success_action_redirect
上傳成功之後,要導向到哪個網頁,如設為:http://localhost:8080/uploaded.html
x-amz-meta-uuid
後端產生的UUID
x-amz-server-side-encryption
說明使用哪種加密方式, AES256
“starts-with”, “$Content-Type”
上傳的 content type,可以不設定
x-amz-credential
由 /<日期>/// 組成,RegionService和Signing是固定的就是s3和aws4_request
x-amz-algorithm
說明建立這個Request的簽名使用哪種演算法,例如 AWS4-HMAC-SHA256
x-amz-date
建立這個Request的簽名的日期,為可選的,需要為 ISO 8601 basic format (YYYYMMDD’T’HHMMSS’Z’) 格式
注意,如果你在HTML Form中沒有設定某些參數,那麼在Policy中也不要設定,例如我在HTML Form中沒有設定 ["starts-with", "$Content-Type", "image/"]
和 ["starts-with", "$x-amz-meta-tag", ""]
,那麼在 Policy中也不要設定。
之後由後端使用Base64將這段字串編碼,這個Base64字串編碼就是要放在 HTML Form 中的 Policy,以下是Java產生的範例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 JSONObject j = new JSONObject ();j.put("expiration" , "2015-12-30T12:00:00.000Z" ); j.put("conditions" , new JSONArray () .put(new JSONObject ().put("bucket" , "sigv4examplebucket" )) .put(new JSONArray ().put("starts-with" ).put("$key" ).put("user/user1/" )) .put(new JSONObject ().put("acl" , "private" )) .put(new JSONObject ().put("success_action_redirect" , "http://localhost:8080" )) .put(new JSONObject ().put("x-amz-meta-uuid" , "14365123651274" )) .put(new JSONObject ().put("x-amz-server-side-encryption" , "AES256" )) .put(new JSONObject ().put("x-amz-credential" , "AKIAIOSFODNN7EXAMPLE/20151229/us-east-1/s3/aws4_request" )) .put(new JSONObject ().put("x-amz-algorithm" , "AWS4-HMAC-SHA256" )) .put(new JSONObject ().put("x-amz-date" , "20151229T000000Z" )) ); String policy = Base64.getEncoder().encodeToString(j.toString().getBytes());
Signature-value 最後產生 Signature
,Java 可以直接使用有人提供的產生方式,來自 https://gist.github.com/phstudy/3523576726d74a0410f8
1 2 3 4 5 6 7 8 9 10 String accessSecretKey = "AWS4" + "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY" ;String date = "20151229" ;String region = "us-east-1" ;String regionService = "s3" ;String signing = "aws4_request" ;String stringToSign = policy;String signature = getSignatureV4(accessSecretKey, date, region, regionService, signing, stringToSign);
以上動作完成之後,還需要為你的Bucket設定 Cross-origin resource sharing (CORS)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 [ { "AllowedHeaders" : [ "*" ] , "AllowedMethods" : [ "POST" , "GET" , "PUT" ] , "AllowedOrigins" : [ "http://www.mywebsite.com/" ] , "ExposeHeaders" : [ ] } ]
Bucket沒有設定Cross-origin resource sharing (CORS)會出現 CORS policy 錯誤
1 Access to XMLHttpRequest at 'https://<你的bucket>.s3.amazonaws.com/' from origin 'https://你的網站.com ' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.
如果是還在測試開發的話,可以先將Chrome的web-security關閉 ,Mac使用以下指令,其他設定可以參考: disable-same-origin-policy-in-chrome
1 open -n -a "Google Chrome" --args --user-data-dir=/tmp/temp_chrome_user_data_dir http://localhost:8080/ --disable-web-security
Reference: