OSS如何限制上傳文件類型及大小?
用戶上傳過大文件或惡意文件,會占用OSS大量的存儲空間和帶寬資源,甚至導(dǎo)致OSS的域名在某些環(huán)境下被封禁。OSS不直接提供限制上傳文件類型和大小的功能,但您可以借助服務(wù)端生成簽名時指定文件類型及大小,或在客戶端自行編寫攔截邏輯來實現(xiàn)。
服務(wù)端生成Post簽名和PostPolicy
對于需要限制上傳文件屬性的場景,您可以在服務(wù)端生成PostObject所需的Post簽名、PostPolicy等信息,然后客戶端可以憑借這些信息,在一定的限制下不依賴OSS SDK直接上傳文件。您可以借助服務(wù)端生成的PostPolicy限制客戶端上傳的文件,例如限制文件大小、文件類型。此方案適用于通過HTML表單上傳的方式上傳文件。需要注意的是,此方案不支持基于分片上傳大文件、基于分片斷點續(xù)傳的場景。更多信息,請參見PostObject。
示例代碼
服務(wù)端生成Post簽名和Post Policy等信息的代碼示例如下:
import os
from hashlib import sha1 as sha
import json
import base64
import hmac
import datetime
import time
# 配置環(huán)境變量OSS_ACCESS_KEY_ID。
access_key_id = os.environ.get('OSS_ACCESS_KEY_ID')
# 配置環(huán)境變量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: 有權(quán)限訪問目標(biāo)Bucket的AccessKeySecret。
:param expiration: 簽名過期時間,按照ISO8601標(biāo)準(zhǔn)表示,并需要使用UTC時間,格式為yyyy-MM-ddTHH:mm:ssZ。示例值:"2014-12-01T12:00:00.000Z"。
:param conditions: 策略條件,用于限制上傳表單時允許設(shè)置的值。
:param policy_extra_props: 額外的policy參數(shù),后續(xù)如果policy新增參數(shù)支持,可以在通過dict傳入額外的參數(shù)。
: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時,上傳成功后的返回狀態(tài)碼,默認(rèn)為 204。
["eq", "$success_action_status", "200"],
# 表單域的值必須以指定前綴開始。例如指定key的值以user/user1開始,則可以寫為["starts-with", "$key", "user/user1"]。
["starts-with", "$key", upload_dir],
# 限制上傳Object的最小和最大允許大小,單位為字節(jié)。
["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
# 可以在這里再自行追加其他參數(shù)
}
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);
});
});
服務(wù)端生成簽名URL
生成簽名URL時,不支持指定content-length-range
,因此不適用于限制上傳文件大小的場景。對于要限制上傳文件類型的場景,您可以在服務(wù)端生成簽名URL時,強(qiáng)制指定content-type
,客戶端憑借簽名URL上傳文件時,必須上傳指定類型的文件。更多信息,請參見簽名版本1。
示例代碼
服務(wù)端生成簽名URL的代碼示例如下:
import oss2
from oss2.credentials import EnvironmentVariableCredentialsProvider
# 從環(huán)境變量中獲取訪問憑證。運行本代碼示例之前,請確保已設(shè)置環(huán)境變量OSS_ACCESS_KEY_ID和OSS_ACCESS_KEY_SECRET。
auth = oss2.ProviderAuth(EnvironmentVariableCredentialsProvider())
# 將<YOUR_ENDPOINT>替換為Bucket所在地域?qū)?yīng)的Endpoint。以華東1(杭州)為例,Endpoint填寫為https://oss-cn-hangzhou.aliyuncs.com。
# 將<YOUR_BUCKET>替換為Bucket名稱。
bucket = oss2.Bucket(auth, '<YOUR_ENDPOINT>', '<YOUR_BUCKET>')
# 指定過期時間,單位秒。
expire_time = 3600
# 填寫Object完整路徑,例如exampledir/exampleobject.png。Object完整路徑中不能包含Bucket名稱。
object_name = 'exampledir/exampleobject.png'
def generate_presigned_url():
# 指定Header。
headers = dict()
# 指定Content-Type。
headers['Content-Type'] = 'image/png'
# 指定存儲類型。
# headers["x-oss-storage-class"] = "Standard"
# 生成簽名URL時,OSS默認(rèn)會對Object完整路徑中的正斜線(/)進(jìn)行轉(zhuǎn)義,從而導(dǎo)致生成的簽名URL無法直接使用。
# 設(shè)置slash_safe為True,OSS不會對Object完整路徑中的正斜線(/)進(jìn)行轉(zhuǎn)義,此時生成的簽名URL可以直接使用。
url = bucket.sign_url('PUT', object_name, expire_time, slash_safe=True, headers=headers)
return url
Web端使用簽名URL上傳文件到OSS的代碼示例如下:
const form = document.querySelector("form");
form.addEventListener("submit", (event) => {
event.preventDefault();
const fileInput = document.querySelector("#file");
const file = fileInput.files[0];
fetch(`/get_presigned_url_for_oss_upload?filename=${file.name}`, { method: "GET" })
.then((response) => {
return response.text();
})
.then((url) => {
fetch(url, {
method: "PUT",
headers: new Headers({
'Content-Type': 'image/png',
}),
body: file,
}).then((res) => {
console.log(res);
alert('文件已上傳');
});
});
});
客戶端自行攔截
對于需要限制上傳文件屬性的場景,您可以在客戶端上通過JavaScript代碼來實現(xiàn)。通過條件語句檢查文件大小和文件類型是否滿足要求,如果不滿足,則彈出警告或錯誤信息,阻止上傳操作。需要注意的是,STS臨時訪問憑證不支持設(shè)置限制上傳文件的大小及類型。因此,如果STS臨時訪問憑證被截取,惡意用戶可能會繞過客戶端限制,直接上傳惡意文件到您的OSS。
示例代碼
使用<input type="file">
元素的files
屬性和accept
屬性來檢查文件大小和文件類型是否符合要求的示例代碼如下:
<!DOCTYPE html>
<html>
<head>
<title>文件上傳</title>
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
</head>
<body>
<input type="file" id="file-upload" accept="image/jpeg, image/png, application/pdf" />
<button onclick="uploadFile()">上傳</button>
<script>
function uploadFile() {
const fileInput = document.getElementById('file-upload');
const file = fileInput.files[0];
// 文件大小限制(單位:字節(jié))
const maxFileSize = 1024 * 1024; // 1MB
// 允許的文件類型
const allowedTypes = ['image/jpeg', 'image/png', 'application/pdf'];
if (file) {
// 檢查文件大小
if (file.size > maxFileSize) {
alert('文件大小超過限制。請上傳小于1MB的文件。');
return;
}
// 檢查文件類型
if (!allowedTypes.includes(file.type)) {
alert('不支持的文件類型。請上傳JPEG、PNG或PDF文件。');
return;
}
// 文件驗證通過,可以進(jìn)行上傳操作
// 這里可以編寫具體的上傳邏輯
console.log('開始上傳文件:', file.name);
// 上傳的代碼
} else {
alert('請選擇要上傳的文件。');
}
}
</script>
</body>
</html>