全部產品
Search
文件中心

Object Storage Service:OSS如何限制上傳檔案類型及大小?

更新時間:Feb 28, 2024

使用者上傳過大檔案或惡意檔案,會佔用OSS大量的儲存空間和頻寬資源,甚至導致OSS的網域名稱在某些環境下被封鎖。OSS不直接提供限制上傳檔案類型和大小的功能,但您可以藉助服務端產生簽名時指定檔案類型及大小,或在用戶端自行編寫攔截邏輯來實現。

服務端產生Post簽名和PostPolicy

對於需要限制上傳檔案屬性的情境,您可以在服務端產生PostObject所需的Post簽名、PostPolicy等資訊,然後用戶端可以憑藉這些資訊,在一定的限制下不依賴OSS SDK直接上傳檔案。您可以藉助服務端產生的PostPolicy限制用戶端上傳的檔案,例如限制檔案大小、檔案類型。此方案適用於通過HTML表單上傳的方式上傳檔案。需要注意的是,此方案不支援基於分區上傳大檔案、基於分區斷點續傳的情境。更多資訊,請參見PostObject

範例程式碼

服務端範例程式碼

服務端產生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);
    });
});

服務端產生簽名URL

產生簽名URL時,不支援指定content-length-range,因此不適用於限制上傳檔案大小的情境。對於要限制上傳檔案類型的情境,您可以在服務端產生簽名URL時,強制指定content-type,用戶端憑藉簽名URL上傳檔案時,必須上傳指定類型的檔案。更多資訊,請參見簽名版本1

範例程式碼

服務端範例程式碼

服務端產生簽名URL的程式碼範例如下:

import oss2
from oss2.credentials import EnvironmentVariableCredentialsProvider

# 從環境變數中擷取訪問憑證。運行本程式碼範例之前,請確保已設定環境變數OSS_ACCESS_KEY_ID和OSS_ACCESS_KEY_SECRET。
auth = oss2.ProviderAuth(EnvironmentVariableCredentialsProvider())
# 將<YOUR_ENDPOINT>替換為Bucket所在地區對應的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預設會對Object完整路徑中的正斜線(/)進行轉義,從而導致產生的簽名URL無法直接使用。
    # 設定slash_safe為True,OSS不會對Object完整路徑中的正斜線(/)進行轉義,此時產生的簽名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代碼來實現。通過條件陳述式檢查檔案大小和檔案類型是否滿足要求,如果不滿足,則彈出警告或錯誤資訊,阻止上傳操作。需要注意的是,STS臨時訪問憑證不支援設定限制上傳檔案的大小及類型。因此,如果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];

      // 檔案大小限制(單位:位元組)
      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;
        }

        // 檔案驗證通過,可以進行上傳操作
        // 這裡可以編寫具體的上傳邏輯
        console.log('開始上傳檔案:', file.name);
        // 上傳的代碼
      } else {
        alert('請選擇要上傳的檔案。');
      }
    }
  </script>
</body>
</html>