Python使用签名URL下载

默认情况下,OSS Bucket中的文件是私有的,仅文件拥有者可访问。您可以使用Go SDK生成带有过期时间的GET方法签名URL,以允许他人临时下载文件。在有效期内可多次访问,超期后需重新生成。

注意事项

  • 本文以华东1(杭州)外网Endpoint为例。如果您希望通过与OSS同地域的其他阿里云产品访问OSS,请使用内网Endpoint。关于OSS支持的Region与Endpoint的对应关系,请参见OSS地域和访问域名

  • 本文以从环境变量读取访问凭证为例。如何配置访问凭证,请参见配置访问凭证

  • 本文以OSS域名新建OSSClient为例。如果您希望通过自定义域名、STS等方式新建OSSClient,请参见新建OSSClient

  • 生成GET方法的签名URL时,您必须具有oss:GetObject权限。具体操作,请参见为RAM用户授权自定义的权限策略

    说明

    生成签名URL过程中,SDK利用本地存储的密钥信息,根据特定算法计算出签名(signature),然后将其附加到URL上,以确保URL的有效性和安全性。这一系列计算和构造URL的操作都是在客户端完成,不涉及网络请求到服务端。因此,生成签名URL时不需要授予调用者特定权限。但是,为避免第三方用户无法对签名URL授权的资源执行相关操作,需要确保调用生成签名URL接口的身份主体被授予对应的权限。

  • 本文以V4签名URL为例,有效期最大为7天。更多信息,请参见签名版本4(推荐)

使用过程

使用签名URL下载文件的过程如下:

image

示例代码

  1. 文件拥有者生成GET方法的签名URL。

    # -*- coding: utf-8 -*-
    import oss2
    from oss2.credentials import EnvironmentVariableCredentialsProvider
    
    # 从环境变量中获取访问凭证。运行本代码示例之前,请确保已设置环境变量OSS_ACCESS_KEY_ID和OSS_ACCESS_KEY_SECRET。
    auth = oss2.ProviderAuthV4(EnvironmentVariableCredentialsProvider())
    
    # 填写Bucket所在地域对应的Endpoint。以华东1(杭州)为例,Endpoint填写为https://oss-cn-hangzhou.aliyuncs.com。
    endpoint = "https://oss-cn-hangzhou.aliyuncs.com"
    
    # 填写Endpoint对应的Region信息,例如cn-hangzhou。注意,v4签名下,必须填写该参数
    region = "cn-hangzhou"
    
    # yourBucketName填写存储空间名称。
    bucket = oss2.Bucket(auth, endpoint, "yourBucketName", region=region)
    
    # 填写Object完整路径,例如exampledir/exampleobject.txt。Object完整路径中不能包含Bucket名称。
    object_name = 'exampledir/exampleobject.txt'
    
    # 指定HTTP查询参数。
    params = dict()
    # 设置单链接限速,单位为bit,例如限速100 KB/s。
    # params['x-oss-traffic-limit'] = str(100 * 1024 * 8)
    # 指定IP地址或者IP地址段。
    # params['x-oss-ac-source-ip'] = "127.0.0.1"
    # 指定子网掩码中1的个数。
    # params['x-oss-ac-subnet-mask'] = "32"
    # 指定VPC ID。
    # params['x-oss-ac-vpc-id'] = "vpc-t4nlw426y44rd3iq4xxxx"
    # 指定是否允许转发请求。
    # params['x-oss-ac-forward-allow'] = "true"
    
    # 生成下载文件的签名URL,有效时间为60秒。
    # 生成签名URL时,OSS默认会对Object完整路径中的正斜线(/)进行转义,从而导致生成的签名URL无法直接使用。
    # 设置slash_safe为True,OSS不会对Object完整路径中的正斜线(/)进行转义,此时生成的签名URL可以直接使用。
    url = bucket.sign_url('GET', object_name, 600, slash_safe=True, params=params)
    print('签名URL的地址为:', url)
  2. 其他人使用GET方法的签名URL下载文件。

    curl

    curl -SO "https://examplebucket.oss-cn-hangzhou.aliyuncs.com/exampleobject.txt?x-oss-date=20241112T092756Z&x-oss-expires=3599&x-oss-signature-version=OSS4-HMAC-SHA256&x-oss-credential=LTAI5************/20241112/cn-hangzhou/oss/aliyun_v4_request&x-oss-signature=ed5a939feb8d79a389572719f7e2939939936d0**********"

    Java

    import java.io.BufferedInputStream;
    import java.io.FileOutputStream;
    import java.io.IOException;
    import java.io.InputStream;
    import java.net.HttpURLConnection;
    import java.net.URL;
    
    public class Demo {
        public static void main(String[] args) {
            // 替换为生成的GET方法的签名URL。
            String fileURL = "https://examplebucket.oss-cn-hangzhou.aliyuncs.com/exampleobject.txt?x-oss-date=20241112T092756Z&x-oss-expires=3599&x-oss-signature-version=OSS4-HMAC-SHA256&x-oss-credential=LTAI5************/20241112/cn-hangzhou/oss/aliyun_v4_request&x-oss-signature=ed5a939feb8d79a389572719f7e2939939936d0**********";
            // 填写文件保存的目标路径,包括文件名和扩展名。
            String savePath = "C:/downloads/myfile.txt";
    
            try {
                downloadFile(fileURL, savePath);
                System.out.println("Download completed!");
            } catch (IOException e) {
                System.err.println("Error during download: " + e.getMessage());
            }
        }
    
        private static void downloadFile(String fileURL, String savePath) throws IOException {
            URL url = new URL(fileURL);
            HttpURLConnection httpConn = (HttpURLConnection) url.openConnection();
            httpConn.setRequestMethod("GET");
    
            // 检查响应代码
            int responseCode = httpConn.getResponseCode();
            if (responseCode == HttpURLConnection.HTTP_OK) {
                // 输入流
                InputStream inputStream = new BufferedInputStream(httpConn.getInputStream());
                // 输出流
                FileOutputStream outputStream = new FileOutputStream(savePath);
    
                byte[] buffer = new byte[4096]; // 缓冲区
                int bytesRead;
                while ((bytesRead = inputStream.read(buffer)) != -1) {
                    outputStream.write(buffer, 0, bytesRead);
                }
    
                outputStream.close();
                inputStream.close();
            } else {
                System.out.println("No file to download. Server replied HTTP code: " + responseCode);
            }
            httpConn.disconnect();
        }
    }

    Node.js

    const https = require('https');
    const fs = require('fs');
    
    const fileURL = "https://examplebucket.oss-cn-hangzhou.aliyuncs.com/exampleobject.txt?x-oss-date=20241112T092756Z&x-oss-expires=3599&x-oss-signature-version=OSS4-HMAC-SHA256&x-oss-credential=LTAI5************/20241112/cn-hangzhou/oss/aliyun_v4_request&x-oss-signature=ed5a939feb8d79a389572719f7e2939939936d0**********";
    const savePath = "C:/downloads/myfile.txt";
    
    https.get(fileURL, (response) => {
        if (response.statusCode === 200) {
            const fileStream = fs.createWriteStream(savePath);
            response.pipe(fileStream);
            
            fileStream.on('finish', () => {
                fileStream.close();
                console.log("Download completed!");
            });
        } else {
            console.error(`Download failed. Server responded with code: ${response.statusCode}`);
        }
    }).on('error', (err) => {
        console.error("Error during download:", err.message);
    });

    Python

    import requests
    
    file_url = "https://examplebucket.oss-cn-hangzhou.aliyuncs.com/exampleobject.txt?x-oss-date=20241112T092756Z&x-oss-expires=3599&x-oss-signature-version=OSS4-HMAC-SHA256&x-oss-credential=LTAI5************/20241112/cn-hangzhou/oss/aliyun_v4_request&x-oss-signature=ed5a939feb8d79a389572719f7e2939939936d0**********"
    save_path = "C:/downloads/myfile.txt"
    
    try:
        response = requests.get(file_url, stream=True)
        if response.status_code == 200:
            with open(save_path, 'wb') as f:
                for chunk in response.iter_content(4096):
                    f.write(chunk)
            print("Download completed!")
        else:
            print(f"No file to download. Server replied HTTP code: {response.status_code}")
    except Exception as e:
        print("Error during download:", e)

    Go

    package main
    
    import (
        "io"
        "net/http"
        "os"
    )
    
    func main() {
        fileURL := "https://examplebucket.oss-cn-hangzhou.aliyuncs.com/exampleobject.txt?x-oss-date=20241112T092756Z&x-oss-expires=3599&x-oss-signature-version=OSS4-HMAC-SHA256&x-oss-credential=LTAI5************/20241112/cn-hangzhou/oss/aliyun_v4_request&x-oss-signature=ed5a939feb8d79a389572719f7e2939939936d0**********"
        savePath := "C:/downloads/myfile.txt"
    
        response, err := http.Get(fileURL)
        if err != nil {
            panic(err)
        }
        defer response.Body.Close()
    
        if response.StatusCode == http.StatusOK {
            outFile, err := os.Create(savePath)
            if err != nil {
                panic(err)
            }
            defer outFile.Close()
    
            _, err = io.Copy(outFile, response.Body)
            if err != nil {
                panic(err)
            }
            println("Download completed!")
        } else {
            println("No file to download. Server replied HTTP code:", response.StatusCode)
        }
    }

    JavaScript

    const fileURL = "https://examplebucket.oss-cn-hangzhou.aliyuncs.com/exampleobject.txt?x-oss-date=20241112T092756Z&x-oss-expires=3599&x-oss-signature-version=OSS4-HMAC-SHA256&x-oss-credential=LTAI5************/20241112/cn-hangzhou/oss/aliyun_v4_request&x-oss-signature=ed5a939feb8d79a389572719f7e2939939936d0**********";
    const savePath = "C:/downloads/myfile.txt"; // 文件将在下载时使用的文件名
    
    fetch(fileURL)
        .then(response => {
            if (!response.ok) {
                throw new Error(`Server replied HTTP code: ${response.status}`);
            }
            return response.blob(); // 将响应转换为 blob
        })
        .then(blob => {
            const link = document.createElement('a');
            link.href = window.URL.createObjectURL(blob);
            link.download = savePath; // 设置下载文件的名字
            document.body.appendChild(link); // 此步骤确保链接存在于文档中
            link.click(); // 模拟点击下载链接
            link.remove(); // 完成后移除链接
            console.log("Download completed!");
        })
        .catch(error => {
            console.error("Error during download:", error);
        });

    Android-Java

    import android.os.AsyncTask;
    import android.os.Environment;
    import java.io.BufferedInputStream;
    import java.io.FileOutputStream;
    import java.io.InputStream;
    import java.net.HttpURLConnection;
    import java.net.URL;
    
    public class DownloadTask extends AsyncTask<String, String, String> {
        @Override
        protected String doInBackground(String... params) {
            String fileURL = params[0];
            String savePath = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS) + "/myfile.txt"; // 修改后的保存路径
            try {
                URL url = new URL(fileURL);
                HttpURLConnection httpConn = (HttpURLConnection) url.openConnection();
                httpConn.setRequestMethod("GET");
                int responseCode = httpConn.getResponseCode();
                if (responseCode == HttpURLConnection.HTTP_OK) {
                    InputStream inputStream = new BufferedInputStream(httpConn.getInputStream());
                    FileOutputStream outputStream = new FileOutputStream(savePath);
                    byte[] buffer = new byte[4096];
                    int bytesRead;
                    while ((bytesRead = inputStream.read(buffer)) != -1) {
                        outputStream.write(buffer, 0, bytesRead);
                    }
                    outputStream.close();
                    inputStream.close();
                    return "Download completed!";
                } else {
                    return "No file to download. Server replied HTTP code: " + responseCode;
                }
            } catch (Exception e) {
                return "Error during download: " + e.getMessage();
            }
        }
    }

    Objective-C

    #import <Foundation/Foundation.h>
    
    int main(int argc, const char * argv[]) {
        @autoreleasepool {
            // 定义文件 URL 和保存路径(修改为有效的路径)
            NSString *fileURL = @"https://examplebucket.oss-cn-hangzhou.aliyuncs.com/exampleobject.txt?x-oss-date=20241112T092756Z&x-oss-expires=3599&x-oss-signature-version=OSS4-HMAC-SHA256&x-oss-credential=LTAI5************/20241112/cn-hangzhou/oss/aliyun_v4_request&x-oss-signature=ed5a939feb8d79a389572719f7e2939939936d0**********";
            NSString *savePath = @"/Users/your_username/Desktop/myfile.txt"; // 请替换为您的用户名
            
            // 创建 URL 对象
            NSURL *url = [NSURL URLWithString:fileURL];
            
            // 创建下载任务
            NSURLSessionDataTask *task = [[NSURLSession sharedSession] dataTaskWithURL:url completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
                // 错误处理
                if (error) {
                    NSLog(@"Error during download: %@", error.localizedDescription);
                    return;
                }
                
                // 检查数据
                if (!data) {
                    NSLog(@"No data received.");
                    return;
                }
                
                // 保存文件
                NSError *writeError = nil;
                BOOL success = [data writeToURL:[NSURL fileURLWithPath:savePath] options:NSDataWritingAtomic error:&writeError];
                if (success) {
                    NSLog(@"Download completed!");
                } else {
                    NSLog(@"Error saving file: %@", writeError.localizedDescription);
                }
            }];
            
            // 启动任务
            [task resume];
            
            // 让主线程继续运行以便异步请求能够完成
            [[NSRunLoop currentRunLoop] run];
        }
        return 0;
    }

其他场景

生成指定版本的文件的GET方法的签名URL

以下代码示例在生成GET方法的签名URL时,指定了文件的版本,以允许他人下载指定版本的文件。

# -*- coding: utf-8 -*-
import oss2
from oss2.credentials import EnvironmentVariableCredentialsProvider
import requests

# 从环境变量中获取访问凭证。运行本代码示例之前,请确保已设置环境变量OSS_ACCESS_KEY_ID和OSS_ACCESS_KEY_SECRET。
auth = oss2.ProviderAuthV4(EnvironmentVariableCredentialsProvider())

# 填写Bucket所在地域对应的Endpoint。以华东1(杭州)为例,Endpoint填写为https://oss-cn-hangzhou.aliyuncs.com。
endpoint = "https://oss-cn-hangzhou.aliyuncs.com"

# 填写Endpoint对应的Region信息,例如cn-hangzhou。注意,v4签名下,必须填写该参数
region = "cn-hangzhou"

# yourBucketName填写存储空间名称。
bucket = oss2.Bucket(auth, endpoint, "yourBucketName", region=region)

# 填写Object完整路径,例如exampledir/exampleobject.txt。Object完整路径中不能包含Bucket名称。
object_name = 'exampledir/exampleobject.txt'

# 指定Header。
headers = dict()
# 填写Object的versionId。
headers["versionId"] = "CAEQARiBgID8rumR2hYiIGUyOTAyZGY2MzU5MjQ5ZjlhYzQzZjNlYTAyZDE3****"

# 生成上传文件的签名URL,有效时间为60秒。
# 生成签名URL时,OSS默认会对Object完整路径中的正斜线(/)进行转义,从而导致生成的签名URL无法直接使用。
# 设置slash_safe为True,OSS不会对Object完整路径中的正斜线(/)进行转义,此时生成的签名URL可以直接使用。
url = bucket.sign_url('PUT', object_name, 60, slash_safe=True, headers=headers)
print('签名URL的地址为:', url)

使用签名URL下载指定请求头的文件

在生成GET方式的签名URL时,如果指定了请求头,确保在通过该签名URL发起GET请求时也包含相应的请求头,以免出现不一致,导致请求失败和签名错误。

  1. 生成带请求头的GET方法签名URL。

    # -*- coding: utf-8 -*-
    import oss2
    from oss2.credentials import EnvironmentVariableCredentialsProvider
    
    # 从环境变量中获取访问凭证。运行本代码示例之前,请确保已设置环境变量OSS_ACCESS_KEY_ID和OSS_ACCESS_KEY_SECRET。
    auth = oss2.ProviderAuthV4(EnvironmentVariableCredentialsProvider())
    
    # 填写Bucket所在地域对应的Endpoint。以华东1(杭州)为例,Endpoint填写为https://oss-cn-hangzhou.aliyuncs.com。
    endpoint = "https://oss-cn-hangzhou.aliyuncs.com"
    
    # 填写Endpoint对应的Region信息,例如cn-hangzhou。注意,v4签名下,必须填写该参数
    region = "cn-hangzhou"
    
    # yourBucketName填写存储空间名称。
    bucket = oss2.Bucket(auth, endpoint, "yourBucketName", region=region)
    
    # 填写Object完整路径,例如exampledir/exampleobject.txt。Object完整路径中不能包含Bucket名称。
    object_name = 'exampledir/exampleobject.txt'
    
    # 指定Header。
    headers = dict()
    # 指定Content-Type。
    headers['Content-Type'] = 'text/plain'
    
    # 指定HTTP查询参数。
    params = dict()
    # 设置单链接限速,单位为bit,例如限速100 KB/s。
    # params['x-oss-traffic-limit'] = str(100 * 1024 * 8)
    # 指定IP地址或者IP地址段。
    # params['x-oss-ac-source-ip'] = "127.0.0.1"
    # 指定子网掩码中1的个数。
    # params['x-oss-ac-subnet-mask'] = "32"
    # 指定VPC ID。
    # params['x-oss-ac-vpc-id'] = "vpc-t4nlw426y44rd3iq4xxxx"
    # 指定是否允许转发请求。
    # params['x-oss-ac-forward-allow'] = "true"
    
    # 生成下载文件的签名URL,有效时间为60秒。
    # 生成签名URL时,OSS默认会对Object完整路径中的正斜线(/)进行转义,从而导致生成的签名URL无法直接使用。
    # 设置slash_safe为True,OSS不会对Object完整路径中的正斜线(/)进行转义,此时生成的签名URL可以直接使用。
    url = bucket.sign_url('GET', object_name, 600, slash_safe=True, headers=headers, params=params)
    print('签名URL的地址为:', url)
  2. 使用签名URL并指定请求头下载文件。

    curl -X GET "https://examplebucket.oss-cn-hangzhou.aliyuncs.com/exampleobject.txt?x-oss-date=20241113T093321Z&x-oss-expires=3599&x-oss-signature-version=OSS4-HMAC-SHA256&x-oss-credential=LTAI5tKHJzUF3wMmACXgf1aH****************&x-oss-signature=f1746f121783eed5dab2d665da95fbca08505263e27476a46f88dbe3702af8a9***************************************" \
    -H "Content-Type: text/plain" \
    -o "myfile.txt"
    import requests
    
    file_url = "https://examplebucket.oss-cn-hangzhou.aliyuncs.com/exampleobject.txt?x-oss-date=20241112T092756Z&x-oss-expires=3599&x-oss-signature-version=OSS4-HMAC-SHA256&x-oss-credential=LTAI5************/20241112/cn-hangzhou/oss/aliyun_v4_request&x-oss-signature=ed5a939feb8d79a389572719f7e2939939936d0**********"
    save_path = "C:/downloads/myfile.txt"
    
    headers = dict()
    headers['Content-Type'] = 'text/plain'
    
    try:
        response = requests.get(file_url, stream=True)
        if response.status_code == 200:
            with open(save_path, 'wb') as f:
                for chunk in response.iter_content(4096):
                    f.write(chunk)
            print("Download completed!")
        else:
            print(f"No file to download. Server replied HTTP code: {response.status_code}")
    except Exception as e:
        print("Error during download:", e)