用戶端直傳是指用戶端直接上傳檔案到Object Storage Service。相對於服務端代理上傳,用戶端直傳避免了商務服務器中轉檔案,提高了上傳速度,節省了伺服器資源。本文介紹用戶端直傳的方案優勢、安全實現和實踐參考。
為什麼用戶端直傳
在典型的服務端和用戶端架構下,常見的檔案上傳方式是服務端代理上傳:用戶端將檔案上傳到商務服務器,然後商務服務器將檔案上傳到OSS。在這個過程中,一份資料需要在網路上傳輸兩次,會造成網路資源的浪費,增大服務端的資源開銷。為瞭解決這一問題,您可以在用戶端直連OSS來完成檔案上傳,無需經過商務服務器中轉。
如何?用戶端直傳
實現用戶端直傳需要解決以下兩大問題:
跨域訪問
如果您的用戶端是Web端或小程式,您需要解決跨域訪問被限制的問題。瀏覽器以及小程式容器出於安全考慮,通常都會限制跨域訪問,這一限制也會限制您的用戶端代碼直連OSS。您可以通過配置OSS Bucket的跨域訪問規則,來允許您的Web端或小程式的網域名稱直接存取OSS。更多資訊,請參見跨網域設定。
安全授權
上傳檔案到OSS需要使用RAM使用者的存取金鑰(AccessKey)來完成簽名認證,但是在用戶端中使用長期有效存取金鑰,可能會導致存取金鑰泄露,進而引起安全問題。為瞭解決這一問題,您可以選擇以下方案實現安全上傳:
服務端產生STS臨時訪問憑證
對於大部分上傳檔案的情境,建議您在服務端使用STS SDK擷取STS臨時訪問憑證,然後在用戶端使用STS臨時憑證和OSS SDK直接上傳檔案。用戶端能重複使用服務端產生的STS臨時訪問憑證產生簽名,因此適用於基於分區上傳大檔案、基於分區斷點續傳的情境。需要注意的是,頻繁地調用STS服務會引起限流,因此建議您對STS臨時憑證做緩衝處理,並在有效期間前重新整理。更多資訊,請參見什麼是STS。
服務端產生PostObject所需的簽名和Post Policy
對於需要限制上傳檔案屬性的情境,您可以在服務端產生PostObject所需的Post簽名、PostPolicy等資訊,然後用戶端可以憑藉這些資訊,在一定的限制下不依賴OSS SDK直接上傳檔案。您可以藉助服務端產生的PostPolicy限制用戶端上傳的檔案,例如限制檔案大小、檔案類型。此方案適用於通過HTML表單上傳的方式上傳檔案。需要注意的是,此方案不支援基於分區上傳大檔案、基於分區斷點續傳的情境。更多資訊,請參見PostObject。
服務端產生PutObject所需的簽名URL
對於簡單上傳檔案的情境,您可以在服務端使用OSS SDK產生PutObject所需的簽名URL,用戶端可以憑藉簽名URL,不依賴OSS SDK直接上傳檔案。需要注意的是,此方案不適用於基於分區上傳大檔案、基於分區斷點續傳的情境。在服務端對每個分區產生簽名URL,並將簽名URL返回給用戶端,會增加與服務端的互動次數和網路請求的複雜性。另外,用戶端可能會修改分區的內容或順序,導致最終合并的檔案不正確。更多資訊,請參見簽名版本1。
服務端產生STS臨時訪問憑證
服務端通過STS臨時訪問憑證授權用戶端上傳檔案到OSS的過程如下。
用戶端向商務服務器請求臨時訪問憑證。
商務服務器使用STS SDK調用AssumeRole介面,擷取臨時訪問憑證。
STS產生並返回臨時訪問憑證給商務服務器。
商務服務器返回臨時訪問憑證給用戶端。
用戶端使用OSS SDK通過該臨時訪問憑證上傳檔案到OSS。
OSS返回成功響應給用戶端。
樣本工程
sts.zip
範例程式碼
用戶端程式碼範例
Web端使用臨時訪問憑證上傳檔案到OSS的程式碼範例如下:
服務端產生PostObject所需的簽名和Post Policy
服務端通過Post簽名和Post Policy授權用戶端上傳檔案到OSS的過程如下。
用戶端向商務服務器請求Post簽名和Post Policy等資訊。
商務服務器產生並返回Post簽名和Post Policy等資訊給用戶端。
用戶端使用Post簽名和Post Policy等資訊調用PostObject通過HTML表單的方式上傳檔案到OSS。
OSS返回成功響應給用戶端。
樣本工程
postsignature.zip
範例程式碼
服務端範例程式碼
服務端產生Post簽名和Post Policy等資訊的程式碼範例如下:
import os
from hashlib import sha1 as sha
import json
import base64
import hmac
import datetime
import time
# 配置環境變數OSS_ACCESS_KEY_ID。
access_key_id = os.environ.get('OSS_ACCESS_KEY_ID')
# 配置環境變數OSS_ACCESS_KEY_SECRET。
access_key_secret = os.environ.get('OSS_ACCESS_KEY_SECRET')
# 將<YOUR_BUCKET>替換為Bucket名稱。
bucket = '<YOUR_BUCKET>'
# host的格式為bucketname.endpoint。將<YOUR_BUCKET>替換為Bucket名稱。將<YOUR_ENDPOINT>替換為OSS Endpoint,例如oss-cn-hangzhou.aliyuncs.com。
host = 'https://<YOUR_BUCKET>.<YOUR_ENDPOINT>'
# 指定上傳到OSS的檔案首碼。
upload_dir = 'user-dir-prefix/'
# 指定到期時間,單位為秒。
expire_time = 3600
def generate_expiration(seconds):
"""
通過指定有效時間長度(秒)產生到期時間。
:param seconds: 有效時間長度(秒)。
:return: ISO8601 時間字串,如:"2014-12-01T12:00:00.000Z"。
"""
now = int(time.time())
expiration_time = now + seconds
gmt = datetime.datetime.utcfromtimestamp(expiration_time).isoformat()
gmt += 'Z'
return gmt
def generate_signature(access_key_secret, expiration, conditions, policy_extra_props=None):
"""
產生簽名字串Signature。
:param access_key_secret: 有許可權訪問目標Bucket的AccessKeySecret。
:param expiration: 簽名到期時間,按照ISO8601標準表示,並需要使用UTC時間,格式為yyyy-MM-ddTHH:mm:ssZ。樣本值:"2014-12-01T12:00:00.000Z"。
:param conditions: 策略條件,用於限制上傳表單時允許設定的值。
:param policy_extra_props: 額外的policy參數,後續如果policy新增參數支援,可以在通過dict傳入額外的參數。
:return: signature,簽名字串。
"""
policy_dict = {
'expiration': expiration,
'conditions': conditions
}
if policy_extra_props is not None:
policy_dict.update(policy_extra_props)
policy = json.dumps(policy_dict).strip()
policy_encode = base64.b64encode(policy.encode())
h = hmac.new(access_key_secret.encode(), policy_encode, sha)
sign_result = base64.b64encode(h.digest()).strip()
return sign_result.decode()
def generate_upload_params():
policy = {
# 有效期間。
"expiration": generate_expiration(expire_time),
# 約束條件。
"conditions": [
# 未指定success_action_redirect時,上傳成功後的返回狀態代碼,預設為 204。
["eq", "$success_action_status", "200"],
# 表單域的值必須以指定首碼開始。例如指定key的值以user/user1開始,則可以寫為["starts-with", "$key", "user/user1"]。
["starts-with", "$key", upload_dir],
# 限制上傳Object的最小和最大允許大小,單位為位元組。
["content-length-range", 1, 1000000],
# 限制上傳的檔案為指定的圖片類型
["in", "$content-type", ["image/jpg", "image/png"]]
]
}
signature = generate_signature(access_key_secret, policy.get('expiration'), policy.get('conditions'))
response = {
'policy': base64.b64encode(json.dumps(policy).encode('utf-8')).decode(),
'ossAccessKeyId': access_key_id,
'signature': signature,
'host': host,
'dir': upload_dir
# 可以在這裡再自行追加其他參數
}
return json.dumps(response)
用戶端範例程式碼
Web端使用Post簽名和Post Policy等資訊上傳檔案到OSS的程式碼範例如下:
const form = document.querySelector('form');
const fileInput = document.querySelector('#file');
form.addEventListener('submit', (event) => {
event.preventDefault();
let file = fileInput.files[0];
let filename = fileInput.files[0].name;
fetch('/get_post_signature_for_oss_upload', { method: 'GET' })
.then(response => response.json())
.then(data => {
const formData = new FormData();
formData.append('name',filename);
formData.append('policy', data.policy);
formData.append('OSSAccessKeyId', data.ossAccessKeyId);
formData.append('success_action_status', '200');
formData.append('signature', data.signature);
formData.append('key', data.dir + filename);
// file必須為最後一個表單域,除file以外的其他表單域無順序要求。
formData.append('file', file);
fetch(data.host, { method: 'POST', body: formData },).then((res) => {
console.log(res);
alert('檔案已上傳');
});
})
.catch(error => {
console.log('Error occurred while getting OSS upload parameters:', error);
});
});
服務端產生PutObject所需的簽名URL
服務端通過簽名URL授權用戶端上傳檔案到OSS的過程如下。
用戶端向商務服務器請求籤名URL。
商務服務器使用OSS SDK產生PUT類型的簽名URL,然後將其返回給用戶端。
用戶端使用PUT類型的簽名URL調用PutObject上傳檔案到OSS。
OSS向用戶端返回成功響應。
樣本工程
presignedurl.zip
範例程式碼
用戶端範例程式碼
Web端使用簽名URL上傳檔案到OSS的程式碼範例如下:
用戶端直傳實踐參考
不同類型的用戶端的直傳實踐參考如下: