全部產品
Search
文件中心

Object Storage Service:.NET使用預簽名URL上傳

更新時間:Mar 06, 2025

預設情況下,OSS Bucket中的檔案是私人的,僅檔案擁有者擁有上傳許可權。您可以使用OSS .NET SDK產生預簽名URL,以允許他人通過該URL上傳檔案。在產生預簽名URL時,可以自訂其到期時間以限制訪問持續時間長度。在預簽名URL有效期間內,該URL可被多次訪問。如果多次執行上傳操作,會有檔案覆蓋的風險。超出有效期間後,將無法進行上傳,此時需要重建預簽名URL。

注意事項

  • 本文以華東1(杭州)外網Endpoint為例。如果您希望通過與OSS同地區的其他阿里雲產品訪問OSS,請使用內網Endpoint。關於OSS支援的Region與Endpoint的對應關係,請參見OSS地區和訪問網域名稱

  • 本文以從環境變數讀取存取憑證為例。如何配置訪問憑證,請參見配置訪問憑證

  • 產生用於上傳的預簽名URL時,您必須具有oss:PutObject許可權。具體操作,請參見為RAM使用者授權自訂的權限原則

    說明

    產生預簽名URL過程中,SDK利用本機存放區的密鑰資訊,根據特定演算法計算出簽名(signature),然後將其附加到URL上,以確保URL的有效性和安全性。這一系列計算和構造URL的操作都是在用戶端完成,不涉及網路請求到服務端。因此,產生預簽名URL時不需要授予調用者特定許可權。但是,為避免第三方使用者無法對預簽名URL授權的資源執行相關操作,需要確保調用產生預簽名URL介面的身份主體被授予對應的許可權。

  • 本文以V4預簽名URL為例,有效期間最大為7天。更多資訊,請參見簽名版本4(推薦)

  • 通過以下樣本產生的預簽名URL中如果包含特殊符號+,可能出現無法正常訪問該簽名URL的現象。如需正常訪問該預簽名URL,請將預簽名URL中的+替換為%2B

  • 如果需要產生HTTPS協議的預簽名URL,請將Endpoint中的通訊協定設定為HTTPS。

使用過程

使用PUT方式的預簽名URL上傳檔案的過程如下:

程式碼範例

以下是使用預簽名URL臨時授權的常見樣本。使用預簽名URL進行臨時授權的完整範例程式碼,請參見GitHub

產生簽名URL並通過簽名URL上傳檔案

  1. 產生用於上傳的預簽名URL

    using Aliyun.OSS;
    using Aliyun.OSS.Common;
    // 填寫Bucket所在地區對應的Endpoint。以華東1(杭州)為例,Endpoint填寫為https://oss-cn-hangzhou.aliyuncs.com。
    var endpoint = "https://oss-cn-hangzhou.aliyuncs.com";
    // 從環境變數中擷取訪問憑證。運行本程式碼範例之前,請確保已設定環境變數OSS_ACCESS_KEY_ID和OSS_ACCESS_KEY_SECRET。
    var accessKeyId = Environment.GetEnvironmentVariable("OSS_ACCESS_KEY_ID");
    var accessKeySecret = Environment.GetEnvironmentVariable("OSS_ACCESS_KEY_SECRET");
    // 填寫Bucket名稱,例如examplebucket。
    var bucketName = "examplebucket";
    // 填寫Object完整路徑,完整路徑中不包含Bucket名稱,例如exampledir/exampleobject.txt。
    var objectName = "exampledir/exampleobject.txt";
    var objectContent = "More than just cloud.";
    // 填寫Bucket所在地區對應的Region。以華東1(杭州)為例,Region填寫為cn-hangzhou。
    const string region = "cn-hangzhou";
    
    // 建立ClientConfiguration執行個體,按照您的需要修改預設參數。
    var conf = new ClientConfiguration();
    
    // 設定v4簽名。
    conf.SignatureVersion = SignatureVersion.V4;
    
    // 建立OssClient執行個體。
    var client = new OssClient(endpoint, accessKeyId, accessKeySecret, conf);
    conf.SetRegion(region);
    try
    {
        // 產生預簽名URL。
        var generatePresignedUriRequest = new GeneratePresignedUriRequest(bucketName, objectName, SignHttpMethod.Put)
        {
            // 設定預簽名URL到期時間,預設值為3600秒。
            Expiration = DateTime.Now.AddHours(1),
        };
        var signedUrl = client.GeneratePresignedUri(generatePresignedUriRequest);
    }
    catch (OssException ex)
    {
        Console.WriteLine("Failed with error code: {0}; Error info: {1}. \nRequestID:{2}\tHostID:{3}",
            ex.ErrorCode, ex.Message, ex.RequestId, ex.HostId);
    }
    catch (Exception ex)
    {
        Console.WriteLine("Failed with error info: {0}", ex.Message);
    }
  2. 其他人使用PUT方法的預簽名URL上傳檔案。

    curl -X PUT -T /path/to/local/file "https://exampleobject.oss-cn-hangzhou.aliyuncs.com/exampleobject.txt?x-oss-date=20241112T083238Z&x-oss-expires=3599&x-oss-signature-version=OSS4-HMAC-SHA256&x-oss-credential=LTAI5************%2F20241112%2Fcn-hangzhou%2Foss%2Faliyun_v4_request&x-oss-signature=ed5a939feb8d79a389572719f7e2939939936d0**********"
    import org.apache.http.HttpEntity;
    import org.apache.http.client.methods.CloseableHttpResponse;
    import org.apache.http.client.methods.HttpPut;
    import org.apache.http.entity.FileEntity;
    import org.apache.http.impl.client.CloseableHttpClient;
    import org.apache.http.impl.client.HttpClients;
    import java.io.*;
    import java.net.URL;
    import java.util.*;
    
    public class SignUrlUpload {
        public static void main(String[] args) throws Throwable {
            CloseableHttpClient httpClient = null;
            CloseableHttpResponse response = null;
    
            // 將<signedUrl>替換為授權URL。
            URL signedUrl = new URL("<signedUrl>");
    
            // 填寫本地檔案的完整路徑。如果未指定本地路徑,則預設從樣本程式所屬專案對應本地路徑中上傳檔案。
            String pathName = "C:\\Users\\demo.txt";
    
            try {
                HttpPut put = new HttpPut(signedUrl.toString());
                System.out.println(put);
                HttpEntity entity = new FileEntity(new File(pathName));
                put.setEntity(entity);
                httpClient = HttpClients.createDefault();
                response = httpClient.execute(put);
    
                System.out.println("返回上傳狀態代碼:"+response.getStatusLine().getStatusCode());
                if(response.getStatusLine().getStatusCode() == 200){
                    System.out.println("使用網路程式庫上傳成功");
                }
                System.out.println(response.toString());
            } catch (Exception e){
                e.printStackTrace();
            } finally {
                response.close();
                httpClient.close();
            }
        }
    }       
    import requests
    
    def upload_file(signed_url, file_path):
        try:
            # 開啟檔案
            with open(file_path, 'rb') as file:
                # 發送PUT請求上傳檔案
                response = requests.put(signed_url, data=file)
         
            print(f"返回上傳狀態代碼:{response.status_code}")
            if response.status_code == 200:
                print("使用網路程式庫上傳成功")
            print(response.text)
     
        except Exception as e:
            print(f"發生錯誤:{e}")
    
    if __name__ == "__main__":
        # 將<signedUrl>替換為授權URL。
        signed_url = "<signedUrl>"
        
        # 填寫本地檔案的完整路徑。如果未指定本地路徑,則預設從樣本程式所屬專案對應本地路徑中上傳檔案。
        file_path = "C:\\Users\\demo.txt"
    
        upload_file(signed_url, file_path)
    
    const fs = require('fs');
    const axios = require('axios');
    
    async function uploadFile(signedUrl, filePath) {
        try {
            // 建立讀取流
            const fileStream = fs.createReadStream(filePath);
            
            // 發送PUT請求上傳檔案
            const response = await axios.put(signedUrl, fileStream, {
                headers: {
                    'Content-Type': 'application/octet-stream' // 根據實際情況調整Content-Type
                }
            });
    
            console.log(`返回上傳狀態代碼:${response.status}`);
            if (response.status === 200) {
                console.log('使用網路程式庫上傳成功');
            }
            console.log(response.data);
        } catch (error) {
            console.error(`發生錯誤:${error.message}`);
        }
    }
    
    // 主函數
    (async () => {
        // 將<signedUrl>替換為授權URL。
        const signedUrl = '<signedUrl>';
        
        // 填寫本地檔案的完整路徑。如果未指定本地路徑,則預設從樣本程式所屬專案對應本地路徑中上傳檔案。
        const filePath = 'C:\\Users\\demo.txt';
    
        await uploadFile(signedUrl, filePath);
    })();
    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>File Upload Example</title>
    </head>
    <body>
        <h1>File Upload Example</h1>
    
        <input type="file" id="fileInput" />
        <button id="uploadButton">Upload File</button>
    
        <script>
            // 請將此替換為實際的預簽名 URL
            const signedUrl = "<signedUrl>"; 
    
            document.getElementById('uploadButton').addEventListener('click', async () => {
                const fileInput = document.getElementById('fileInput');
                const file = fileInput.files[0];
    
                if (file) {
                    try {
                        await upload(file, signedUrl);
                    } catch (error) {
                        console.error('Error during upload:', error);
                        alert('Upload failed: ' + error.message);
                    }
                } else {
                    console.error('Please select a file');
                    alert('Please select a file to upload.');
                }
            });
    
            const upload = async (file, presignedUrl) => {
                const chunkSize = 1024 * 1024;
                let start = 0;
                let end = chunkSize;
    
                while (start < file.size) {
                    const chunk = file.slice(start, end);
                    const response = await fetch(presignedUrl, {
                        method: 'PUT',
                        body: chunk
                    });
    
                    if (!response.ok) {
                        throw new Error(`Upload failed for chunk, status: ${response.status}`);
                    }
    
                    console.log('Chunk uploaded successfully');
                    start = end;
                    end = start + chunkSize;
                }
    
                console.log('File uploaded successfully');
            };
        </script>
    </body>
    </html>
    #include <iostream>
    #include <fstream>
    #include <curl/curl.h>
    
    void uploadFile(const std::string& signedUrl, const std::string& filePath) {
        CURL *curl;
        CURLcode res;
    
        curl_global_init(CURL_GLOBAL_DEFAULT);
        curl = curl_easy_init();
    
        if (curl) {
            // 設定URL
            curl_easy_setopt(curl, CURLOPT_URL, signedUrl.c_str());
    
            // 佈建要求方法為PUT
            curl_easy_setopt(curl, CURLOPT_UPLOAD, 1L);
    
            // 開啟檔案
            FILE *file = fopen(filePath.c_str(), "rb");
            if (!file) {
                std::cerr << "無法開啟檔案: " << filePath << std::endl;
                return;
            }
    
            // 擷取檔案大小
            fseek(file, 0, SEEK_END);
            long fileSize = ftell(file);
            fseek(file, 0, SEEK_SET);
    
            // 設定檔案大小
            curl_easy_setopt(curl, CURLOPT_INFILESIZE_LARGE, (curl_off_t)fileSize);
    
            // 設定輸入檔案控制代碼
            curl_easy_setopt(curl, CURLOPT_READDATA, file);
    
            // 執行請求
            res = curl_easy_perform(curl);
    
            if (res != CURLE_OK) {
                std::cerr << "curl_easy_perform() 失敗: " << curl_easy_strerror(res) << std::endl;
            } else {
                long httpCode = 0;
                curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &httpCode);
                std::cout << "返回上傳狀態代碼: " << httpCode << std::endl;
    
                if (httpCode == 200) {
                    std::cout << "使用網路程式庫上傳成功" << std::endl;
                }
            }
    
            // 關閉檔案
            fclose(file);
    
            // 清理
            curl_easy_cleanup(curl);
        }
    
        curl_global_cleanup();
    }
    
    int main() {
        // 將<signedUrl>替換為授權URL。
        std::string signedUrl = "<signedUrl>";
    
        // 填寫本地檔案的完整路徑。如果未指定本地路徑,則預設從樣本程式所屬專案對應本地路徑中上傳檔案。
        std::string filePath = "C:\\Users\\demo.txt";
    
        uploadFile(signedUrl, filePath);
    
        return 0;
    }
    
    package main
    
    import (
    	"fmt"
    	"io"
    	"net/http"
    	"os"
    )
    
    func uploadFile(signedUrl, filePath string) error {
    	// 開啟檔案
    	file, err := os.Open(filePath)
    	if err != nil {
    		return fmt.Errorf("無法開啟檔案: %w", err)
    	}
    	defer file.Close()
    
    	// 建立一個新的HTTP用戶端
    	client := &http.Client{}
    
    	// 建立一個PUT請求
    	req, err := http.NewRequest("PUT", signedUrl, file)
    	if err != nil {
    		return fmt.Errorf("建立請求失敗: %w", err)
    	}
    
    	// 發送請求
    	resp, err := client.Do(req)
    	if err != nil {
    		return fmt.Errorf("發送請求失敗: %w", err)
    	}
    	defer resp.Body.Close()
    
    	// 讀取響應
    	body, err := io.ReadAll(resp.Body)
    	if err != nil {
    		return fmt.Errorf("讀取響應失敗: %w", err)
    	}
    
    	fmt.Printf("返回上傳狀態代碼: %d\n", resp.StatusCode)
    	if resp.StatusCode == 200 {
    		fmt.Println("使用網路程式庫上傳成功")
    	}
    	fmt.Println(string(body))
    
    	return nil
    }
    
    func main() {
    	// 將<signedUrl>替換為授權URL。
    	signedUrl := "<signedUrl>"
    
    	// 填寫本地檔案的完整路徑。如果未指定本地路徑,則預設從樣本程式所屬專案對應本地路徑中上傳檔案。
    	filePath := "C:\\Users\\demo.txt"
    
    	err := uploadFile(signedUrl, filePath)
    	if err != nil {
    		fmt.Println("發生錯誤:", err)
    	}
    }
    
    package com.example.signurlupload;
    
    import android.os.AsyncTask;
    import android.util.Log;
    
    import java.io.DataOutputStream;
    import java.io.FileInputStream;
    import java.io.IOException;
    import java.net.HttpURLConnection;
    import java.net.URL;
    
    public class SignUrlUploadActivity {
    
        private static final String TAG = "SignUrlUploadActivity";
    
        public void uploadFile(String signedUrl, String filePath) {
            new UploadTask().execute(signedUrl, filePath);
        }
    
        private class UploadTask extends AsyncTask<String, Void, String> {
    
            @Override
            protected String doInBackground(String... params) {
                String signedUrl = params[0];
                String filePath = params[1];
    
                HttpURLConnection connection = null;
                DataOutputStream dos = null;
                FileInputStream fis = null;
    
                try {
                    URL url = new URL(signedUrl);
                    connection = (HttpURLConnection) url.openConnection();
                    connection.setRequestMethod("PUT");
                    connection.setDoOutput(true);
                    connection.setRequestProperty("Content-Type", "application/octet-stream");
    
                    fis = new FileInputStream(filePath);
                    dos = new DataOutputStream(connection.getOutputStream());
    
                    byte[] buffer = new byte[1024];
                    int length;
    
                    while ((length = fis.read(buffer)) != -1) {
                        dos.write(buffer, 0, length);
                    }
    
                    dos.flush();
                    dos.close();
                    fis.close();
    
                    int responseCode = connection.getResponseCode();
                    Log.d(TAG, "返回上傳狀態代碼: " + responseCode);
    
                    if (responseCode == 200) {
                        Log.d(TAG, "使用網路程式庫上傳成功");
                    }
    
                    return "上傳完成,狀態代碼: " + responseCode;
    
                } catch (IOException e) {
                    e.printStackTrace();
                    return "上傳失敗: " + e.getMessage();
                } finally {
                    if (connection != null) {
                        connection.disconnect();
                    }
                }
            }
    
            @Override
            protected void onPostExecute(String result) {
                Log.d(TAG, result);
            }
        }
    
        public static void main(String[] args) {
            SignUrlUploadActivity activity = new SignUrlUploadActivity();
            // 將<signedUrl>替換為授權URL。
            String signedUrl = "<signedUrl>";
            // 填寫本地檔案的完整路徑。如果未指定本地路徑,則預設從樣本程式所屬專案對應本地路徑中上傳檔案。
            String filePath = "C:\\Users\\demo.txt";
            activity.uploadFile(signedUrl, filePath);
        }
    }
    

常見問題

使用臨時簽名進行檔案上傳時,在上傳過程中籤名到期了,上傳中的檔案會失敗嗎?

上傳時不會失敗。

上傳時使用的是預簽名地址,該URL只要是在有效期間裡(取Token的有效期間和預簽名有效期間最小值),都可以使用。

產生預簽名URL時是否支援使用POST方法?

不支援。

產生預簽名URL時僅支援使用PUT和GET方法。如果您需要通過POST方法進行上傳,您需要參考PostObject自行構造POST請求。