使用预签名URL上传文件

更新时间:2025-04-30 02:03:55

默认情况下,OSS存储空间中文件的读写权限是私有,仅文件拥有者可以上传。但文件拥有者可以对指定的文件生成具有临时访问权限的预签名URL,以允许他人使用该预签名URL在有效期内上传文件。该功能适用于授权合作伙伴上传合同,用户上传头像等场景。

注意事项

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

  • 预签名URL无需权限即可生成,但仅当您拥有oss:PutObject权限时,第三方才能通过该预签名URL成功上传文件。具体授权操作,请参见RAM用户授权自定义的权限策略

  • 预签名URL上传不支持上传FormData格式,若需使用FormData上传数据,建议使用OSS表单上传

流程概览

使用预签名URL上传文件的过程如下:

image

使用预签名URL上传单个文件

  1. Bucket拥有者生成PUT方法的预签名URL

    说明

    通过SDK生成的预签名URL,最大有效时长为7天。若使用STSToken来生成预签名URL,则最大有效时长为43200秒(12小时)。

    Java
    Go
    Python
    Node.js
    PHP
    Android
    C++
    iOS
    .NET
    C

    更多SDK信息,请参见Java使用预签名URL上传文件

    import com.aliyun.oss.*;
    import com.aliyun.oss.common.auth.*;
    import com.aliyun.oss.common.comm.SignVersion;
    import com.aliyun.oss.model.GeneratePresignedUrlRequest;
    import java.net.URL;
    import java.util.*;
    import java.util.Date;
    
    public class GetSignUrl {
        public static void main(String[] args) throws Throwable {
            // 以华东1(杭州)的外网Endpoint为例,其它Region请按实际情况填写。
            String endpoint = "https://oss-cn-hangzhou.aliyuncs.com";
            // 从环境变量中获取访问凭证。运行本代码示例之前,请确保已设置环境变量OSS_ACCESS_KEY_ID和OSS_ACCESS_KEY_SECRET。
            EnvironmentVariableCredentialsProvider credentialsProvider = CredentialsProviderFactory.newEnvironmentVariableCredentialsProvider();
            // 填写Bucket名称,例如examplebucket。
            String bucketName = "examplebucket";
            // 填写Object完整路径,例如exampleobject.txt。Object完整路径中不能包含Bucket名称。
            String objectName = "exampleobject.txt";
            // 填写Bucket所在地域。以华东1(杭州)为例,Region填写为cn-hangzhou。
            String region = "cn-hangzhou";
    
            // 创建OSSClient实例。
            ClientBuilderConfiguration clientBuilderConfiguration = new ClientBuilderConfiguration();
            clientBuilderConfiguration.setSignatureVersion(SignVersion.V4);
            OSS ossClient = OSSClientBuilder.create()
                    .endpoint(endpoint)
                    .credentialsProvider(credentialsProvider)
                    .clientConfiguration(clientBuilderConfiguration)
                    .region(region)
                    .build();
    
            URL signedUrl = null;
            try {
                // 指定生成的预签名URL过期时间,单位为毫秒。本示例以设置过期时间为1小时为例。
                Date expiration = new Date(new Date().getTime() + 3600 * 1000L);
    
                // 生成预签名URL。
                GeneratePresignedUrlRequest request = new GeneratePresignedUrlRequest(bucketName, objectName, HttpMethod.PUT);
                // 设置过期时间。
                request.setExpiration(expiration);
                // 通过HTTP PUT请求生成预签名URL。
                signedUrl = ossClient.generatePresignedUrl(request);
                // 打印预签名URL。
                System.out.println("signed url for putObject: " + signedUrl);
    
            } catch (OSSException oe) {
                System.out.println("Caught an OSSException, which means your request made it to OSS, "
                        + "but was rejected with an error response for some reason.");
                System.out.println("Error Message:" + oe.getErrorMessage());
                System.out.println("Error Code:" + oe.getErrorCode());
                System.out.println("Request ID:" + oe.getRequestId());
                System.out.println("Host ID:" + oe.getHostId());
            } catch (ClientException ce) {
                System.out.println("Caught an ClientException, which means the client encountered "
                        + "a serious internal problem while trying to communicate with OSS, "
                        + "such as not being able to access the network.");
                System.out.println("Error Message:" + ce.getMessage());
            }
        }
    }       

    更多SDK信息,请参见Go使用预签名URL上传文件。

    package main
    
    import (
    	"context"
    	"flag"
    	"log"
    	"time"
    
    	"github.com/aliyun/alibabacloud-oss-go-sdk-v2/oss"
    	"github.com/aliyun/alibabacloud-oss-go-sdk-v2/oss/credentials"
    )
    
    // 定义全局变量
    var (
    	region     string // 存储区域
    	bucketName string // 存储空间名称
    	objectName string // 对象名称
    )
    
    // init函数用于初始化命令行参数
    func init() {
    	flag.StringVar(&region, "region", "", "The region in which the bucket is located.")
    	flag.StringVar(&bucketName, "bucket", "", "The name of the bucket.")
    	flag.StringVar(&objectName, "object", "", "The name of the object.")
    }
    
    func main() {
    	// 解析命令行参数
    	flag.Parse()
    
    	// 检查bucket名称是否为空
    	if len(bucketName) == 0 {
    		flag.PrintDefaults()
    		log.Fatalf("invalid parameters, bucket name required")
    	}
    
    	// 检查region是否为空
    	if len(region) == 0 {
    		flag.PrintDefaults()
    		log.Fatalf("invalid parameters, region required")
    	}
    
    	// 检查object名称是否为空
    	if len(objectName) == 0 {
    		flag.PrintDefaults()
    		log.Fatalf("invalid parameters, object name required")
    	}
    
    	// 加载默认配置并设置凭证提供者和区域
    	cfg := oss.LoadDefaultConfig().
    		WithCredentialsProvider(credentials.NewEnvironmentVariableCredentialsProvider()).
    		WithRegion(region)
    
    	// 创建OSS客户端
    	client := oss.NewClient(cfg)
    
    	// 生成PutObject的预签名URL
    	result, err := client.Presign(context.TODO(), &oss.PutObjectRequest{
    		Bucket: oss.Ptr(bucketName),
    		Key:    oss.Ptr(objectName),
    	},
    		oss.PresignExpires(10*time.Minute),
    	)
    	if err != nil {
    		log.Fatalf("failed to put object presign %v", err)
    	}
    
    	log.Printf("request method:%v\n", result.Method)
    	log.Printf("request expiration:%v\n", result.Expiration)
    	log.Printf("request url:%v\n", result.URL)
    	if len(result.SignedHeaders) > 0 {
    		//当返回结果包含签名头时,使用签名URL发送Put请求时,需要设置相应的请求头
    		log.Printf("signed headers:\n")
    		for k, v := range result.SignedHeaders {
    			log.Printf("%v: %v\n", k, v)
    		}
    	}
    }
    

    更多SDK信息,请参见Python使用预签名URL上传文件

    import argparse
    import requests
    import alibabacloud_oss_v2 as oss
    
    from datetime import datetime, timedelta
    
    # 创建命令行参数解析器,并描述脚本用途:生成对象预签名PUT请求链接(Presign Put Object)
    parser = argparse.ArgumentParser(description="presign put object sample")
    
    # 定义命令行参数,包括必需的区域、存储空间名称、endpoint以及对象键名
    parser.add_argument('--region', help='The region in which the bucket is located.', required=True)
    parser.add_argument('--bucket', help='The name of the bucket.', required=True)
    parser.add_argument('--endpoint', help='The domain names that other services can use to access OSS')
    parser.add_argument('--key', help='The name of the object.', required=True)
    
    def main():
        # 解析命令行参数,获取用户输入的值
        args = parser.parse_args()
    
        # 从环境变量中加载访问凭证信息,用于身份验证
        credentials_provider = oss.credentials.EnvironmentVariableCredentialsProvider()
    
        # 使用SDK默认配置创建配置对象,并设置认证提供者
        cfg = oss.config.load_default()
        cfg.credentials_provider = credentials_provider
    
        # 设置配置对象的区域属性,根据用户提供的命令行参数
        cfg.region = args.region
    
        # 如果提供了自定义endpoint,则更新配置对象中的endpoint属性
        if args.endpoint is not None:
            cfg.endpoint = args.endpoint
    
        # 使用上述配置初始化OSS客户端,准备与OSS交互
        client = oss.Client(cfg)
    
        # 发送请求以生成指定对象的预签名PUT请求
        pre_result = client.presign(oss.PutObjectRequest(
            bucket=args.bucket,  # 存储空间名
            key=args.key,  # 对象键名
        ),expires=timedelta(seconds=3600)) # 设置过期时间,单位为秒,此处设置为3600秒
    
        # 打印预签名请求的方法、过期时间和URL,以便确认预签名链接的有效性
        print(f'method: {pre_result.method},'
              f' expiration: {pre_result.expiration.strftime("%Y-%m-%dT%H:%M:%S.000Z")},'
              f' url: {pre_result.url}'
              )
    
        # 打印预签名请求的已签名头信息,这些信息在发送实际请求时会被包含在HTTP头部
        for key, value in pre_result.signed_headers.items():
            print(f'signed headers key: {key}, signed headers value: {value}')
    
    # 当此脚本被直接执行时,调用main函数开始处理逻辑
    if __name__ == "__main__":
        main()  # 脚本入口点,控制程序流程从这里开始

    此处仅列举普通场景,如何生成带图片处理参数的预签名URL、如何生成带versionID的预签名URL,请参见Node.js使用预签名URL上传

    const OSS = require("ali-oss");
    
    // 定义一个生成签名 URL 的函数
    async function generateSignatureUrl(fileName) {
      // 获取预签名URL
      const client = await new OSS({
          accessKeyId: 'yourAccessKeyId',
          accessKeySecret: 'yourAccessKeySecret',
          bucket: 'examplebucket',
          region: 'oss-cn-hangzhou',
          authorizationV4: true
      });
    
      return await client.signatureUrlV4('PUT', 3600, {
          headers: {} // 请根据实际发送的请求头设置此处的请求头
      }, fileName);
    }
    // 调用函数并传入文件名
    generateSignatureUrl('yourFileName').then(url => {
      console.log('Generated Signature URL:', url);
    }).catch(err => {
      console.error('Error generating signature URL:', err);
    });

    此处仅列举普通场景,如何生成带versionID的预签名URL,请参见PHP使用预签名URL上传

    <?php
    if (is_file(__DIR__ . '/../autoload.php')) {
        require_once __DIR__ . '/../autoload.php';
    }
    if (is_file(__DIR__ . '/../vendor/autoload.php')) {
        require_once __DIR__ . '/../vendor/autoload.php';
    }
    
    use OSS\OssClient;
    use OSS\Core\OssException;
    use OSS\Http\RequestCore;
    use OSS\Http\ResponseCore;
    use OSS\Credentials\EnvironmentVariableCredentialsProvider;
    
    // 从环境变量中获取访问凭证。运行本代码示例之前,请确保已设置环境变量OSS_ACCESS_KEY_ID和OSS_ACCESS_KEY_SECRET。
    $provider = new EnvironmentVariableCredentialsProvider();
    // yourEndpoint填写Bucket所在地域对应的Endpoint。以华东1(杭州)为例,Endpoint填写为https://oss-cn-hangzhou.aliyuncs.com。
    $endpoint = "yourEndpoint";
    // 填写Bucket名称。
    $bucket= "examplebucket";
    // 填写不包含Bucket名称在内的Object完整路径。
    $object = "exampleobject.txt";
    // 指定预签名URL的过期时间为600s(最长可达32400s)。
    $timeout = 600;
    try {
        $config = array(  
            "provider" => $provider,
            "endpoint" => $endpoint,
            'signatureVersion'=>OssClient::OSS_SIGNATURE_VERSION_V4,
            "region"=> "cn-hangzhou"
        );
        $ossClient = new OssClient($config);
        // 生成预签名URL。
        $signedUrl = $ossClient->signUrl($bucket, $object, $timeout, "PUT");
        print_r($signedUrl);
    } catch (OssException $e) {
        printf(__FUNCTION__ . ": FAILED\n");
        printf($e->getMessage() . "\n");
        return;
    }           

    更多SDK信息,请参见Android使用预签名URL上传文件

    // 填写Bucket名称,例如examplebucket。
    String bucketName = "examplebucket";
    // 填写不包含Bucket名称在内源Object的完整路径,例如exampleobject.txt。
    String objectKey = "exampleobject.txt";
    // 设置content-type。
    String contentType = "application/octet-stream";
    String url = null;
    try {
        // 生成用于上传文件的预签名URL。
        GeneratePresignedUrlRequest request = new GeneratePresignedUrlRequest(bucketName, objectKey);
        // 设置预签名URL的过期时间为30分钟。
        request.setExpiration(30*60);
        request.setContentType(contentType);    
        request.setMethod(HttpMethod.PUT);
        url = oss.presignConstrainedObjectURL(request);
        Log.d("url", url);
    } catch (ClientException e) {
        e.printStackTrace();
    }

    更多SDK信息,请参见C++使用预签名URL上传文件

    #include <alibabacloud/oss/OssClient.h>
    using namespace AlibabaCloud::OSS;
    
    int main(void)
    {
        /* 初始化OSS账号信息。*/
                
        /* yourEndpoint填写Bucket所在地域对应的Endpoint。以华东1(杭州)为例,Endpoint填写为https://oss-cn-hangzhou.aliyuncs.com。*/
        std::string Endpoint = "yourEndpoint";
        /* yourRegion填写Bucket所在地域对应的Region。以华东1(杭州)为例,Region填写为cn-hangzhou。*/
        std::string Region = "yourRegion";
        /* 填写Bucket名称,例如examplebucket。*/
        std::string BucketName = "examplebucket";
        /* 填写Object完整路径,完整路径中不能包含Bucket名称,例如exampledir/exampleobject.txt。*/   
        std::string PutobjectUrlName = "exampledir/exampleobject.txt";
    
         /* 初始化网络等资源。*/
        InitializeSdk();
    
        ClientConfiguration conf;
        conf.signatureVersion = SignatureVersionType::V4;
        /* 从环境变量中获取访问凭证。运行本代码示例之前,请确保已设置环境变量OSS_ACCESS_KEY_ID和OSS_ACCESS_KEY_SECRET。*/
        auto credentialsProvider = std::make_shared<EnvironmentVariableCredentialsProvider>();
        OssClient client(Endpoint, credentialsProvider, conf);
        client.SetRegion(Region);
    
        /* 设置签名有效时长,最大有效时间为32400秒。*/
        std::time_t t = std::time(nullptr) + 1200;
        /* 生成预签名URL。*/
        auto genOutcome = client.GeneratePresignedUrl(BucketName, PutobjectUrlName, t, Http::Put);
        if (genOutcome.isSuccess()) {
            std::cout << "GeneratePresignedUrl success, Gen url:" << genOutcome.result().c_str() << std::endl;
        }
        else {
            /* 异常处理。*/
            std::cout << "GeneratePresignedUrl fail" <<
            ",code:" << genOutcome.error().Code() <<
            ",message:" << genOutcome.error().Message() <<
            ",requestId:" << genOutcome.error().RequestId() << std::endl;
            return -1;
        }
    
        /* 释放网络等资源。*/
        ShutdownSdk();
        return 0;
    }

    更多SDK信息,请参见iOS使用预签名URL上传文件

    // 填写Bucket名称。
    NSString *bucketName = @"examplebucket";
    // 填写Object名称。
    NSString *objectKey = @"exampleobject.txt";
    NSURL *file = [NSURL fileURLWithPath:@"<filePath>"];
    NSString *contentType = [OSSUtil detemineMimeTypeForFilePath:file.absoluteString uploadName:objectKey];
    __block NSString *urlString;
    // 生成用于上传的预签名URL,并指定预签名URL过期时间为30分钟。
    OSSTask *task = [client presignConstrainURLWithBucketName:bucketName
                                                withObjectKey:objectKey
                                                   httpMethod:@"PUT"
                                       withExpirationInterval:30 * 60
                                               withParameters:@{}
                                                  contentType:contentType
                                                   contentMd5:nil];
    [task continueWithBlock:^id _Nullable(OSSTask * _Nonnull task) {
        if (task.error) {
            NSLog(@"presign error: %@", task.error);
        } else {
            urlString = task.result;
            NSLog(@"url: %@", urlString);
        }
        return nil;
    }];

    更多SDK信息,请参见.NET使用预签名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);
    }

    更多SDK信息,请参见C使用预签名URL下载文件

    #include "oss_api.h"
    #include "aos_http_io.h"
    /* yourEndpoint填写Bucket所在地域对应的Endpoint。以华东1(杭州)为例,Endpoint填写为https://oss-cn-hangzhou.aliyuncs.com。*/
    const char *endpoint = "yourEndpoint";
    
    /* 填写Bucket名称,例如examplebucket。*/
    const char *bucket_name = "examplebucket";
    /* 填写Object完整路径,完整路径中不能包含Bucket名称,例如exampledir/exampleobject.txt。*/
    const char *object_name = "exampledir/exampleobject.txt";
    /* 填写本地文件的完整路径。*/
    const char *local_filename = "yourLocalFilename";
    void init_options(oss_request_options_t *options)
    {
        options->config = oss_config_create(options->pool);
        /* 用char*类型的字符串初始化aos_string_t类型。*/
        aos_str_set(&options->config->endpoint, endpoint);
        /* 从环境变量中获取访问凭证。运行本代码示例之前,请确保已设置环境变量OSS_ACCESS_KEY_ID和OSS_ACCESS_KEY_SECRET。*/
        aos_str_set(&options->config->access_key_id, getenv("OSS_ACCESS_KEY_ID"));
        aos_str_set(&options->config->access_key_secret, getenv("OSS_ACCESS_KEY_SECRET"));
        /* 是否使用CNAME访问OSS服务。0表示不使用。*/
        options->config->is_cname = 0;
        /* 设置网络相关参数,例如超时时间等。*/
        options->ctl = aos_http_controller_create(options->pool, 0);
    }
    int main(int argc, char *argv[])
    {
        /* 在程序入口调用aos_http_io_initialize方法来初始化网络、内存等全局资源。*/
        if (aos_http_io_initialize(NULL, 0) != AOSE_OK) {
            exit(1);
        }
        /* 用于内存管理的内存池(pool),等价于apr_pool_t。其实现代码在apr库中。*/
        aos_pool_t *pool;
        /* 重新创建一个内存池,第二个参数是NULL,表示没有继承其它内存池。*/
        aos_pool_create(&pool, NULL);
        /* 创建并初始化options,该参数包括endpoint、access_key_id、acces_key_secret、is_cname、curl等全局配置信息。*/
        oss_request_options_t *oss_client_options;
        /* 在内存池中分配内存给options。*/
        oss_client_options = oss_request_options_create(pool);
        /* 初始化Client的选项oss_client_options。*/
        init_options(oss_client_options);
        /* 初始化参数。*/
        aos_string_t bucket;
        aos_string_t object;
        aos_string_t file;    
        aos_http_request_t *req;
        apr_time_t now;
        char *url_str;
        aos_string_t url;
        int64_t expire_time; 
        int one_hour = 3600;
        aos_str_set(&bucket, bucket_name);
        aos_str_set(&object, object_name);
        aos_str_set(&file, local_filename);
        expire_time = now / 1000000 + one_hour;    
        req = aos_http_request_create(pool);
        req->method = HTTP_PUT;
        now = apr_time_now(); 
        /* 单位:微秒 */
        expire_time = now / 1000000 + one_hour;
        /* 生成预签名URL。*/
        url_str = oss_gen_signed_url(oss_client_options, &bucket, &object, expire_time, req);
        aos_str_set(&url, url_str);
        printf("临时上传URL: %s\n", url_str);    
        /* 释放内存池,相当于释放了请求过程中各资源分配的内存。*/
        aos_pool_destroy(pool);
        /* 释放之前分配的全局资源。*/
        aos_http_io_deinitialize();
        return 0;
    }
  1. 其他人使用PUT方法生成的预签名URL上传文件。

    重要

    预签名URL在有效期内可多次访问,但多次执行上传操作,会有文件覆盖的风险。超期后,需执行步骤一重新生成预签名URL以继续访问文件。

    curl
    Java
    Go
    python
    Node.js
    browser.js
    C++
    Android
    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=LTAI****************%2F20241112%2Fcn-hangzhou%2Foss%2Faliyun_v4_request&x-oss-signature=ed5a******************************************************"
    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();
            }
        }
    }       
    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)
    	}
    }
    
    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);
    })();
    重要

    如果您使用 Browser.js 上传文件时遇到 403 签名不匹配错误,通常是因为浏览器会自动添加 Content-Type 请求头,而生成预签名 URL 时未指定该请求头,导致签名验证失败。为解决此问题,您需要在生成预签名 URL 时指定 Content-Type 请求头。

    <!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) {
                    alert('Please select a file to upload.');
                    return;
                }
    
                try {
                    await upload(file, signedUrl);
                    alert('File uploaded successfully!');
                } catch (error) {
                    console.error('Error during upload:', error);
                    alert('Upload failed: ' + error.message);
                }
            });
    
            /**
             * 上传文件到 OSS
             * @param {File} file - 需要上传的文件
             * @param {string} presignedUrl - 预签名 URL
             */
            const upload = async (file, presignedUrl) => {
                const response = await fetch(presignedUrl, {
                    method: 'PUT',
                    body: file,  // 直接上传整个文件
                });
    
                if (!response.ok) {
                    throw new Error(`Upload failed, status: ${response.status}`);
                }
    
                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 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);
        }
    }
    

设置Header定义上传策略

您可以在生成预签名URL时,通过指定Header参数定义上传策略。例如,您可以设置文件存储类型x-oss-storage-class和文件类型Content-Type(如下代码示例)。

重要

生成预签名URL时指定了Header参数,实际使用预签名URL上传文件时,也必须传递相同的Header。否则,OSS将因为签名校验失败返回403错误。

关于可设置的OSS系统Header,请参见PutObject。您还可以设置自定义Header来管理文件,详情请参见管理文件元数据

  1. 文件拥有者生成带Header参数的预签名URL。

    Java
    Go
    Python
    import com.aliyun.oss.*;
    import com.aliyun.oss.common.auth.*;
    import com.aliyun.oss.common.comm.SignVersion;
    import com.aliyun.oss.model.GeneratePresignedUrlRequest;
    import java.net.URL;
    import java.util.*;
    import java.util.Date;
    
    public class GetSignUrl {
        public static void main(String[] args) throws Throwable {
            // 以华东1(杭州)的外网Endpoint为例,其它Region请按实际情况填写。
            String endpoint = "https://oss-cn-hangzhou.aliyuncs.com";
            // 从环境变量中获取访问凭证。运行本代码示例之前,请确保已设置环境变量OSS_ACCESS_KEY_ID和OSS_ACCESS_KEY_SECRET。
            EnvironmentVariableCredentialsProvider credentialsProvider = CredentialsProviderFactory.newEnvironmentVariableCredentialsProvider();
            // 填写Bucket名称,例如examplebucket。
            String bucketName = "examplebucket";
            // 填写Object完整路径,例如exampleobject.txt。Object完整路径中不能包含Bucket名称。
            String objectName = "exampleobject.txt";
            // 填写Bucket所在地域。以华东1(杭州)为例,Region填写为cn-hangzhou。
            String region = "cn-hangzhou";
    
            // 创建OSSClient实例。
            ClientBuilderConfiguration clientBuilderConfiguration = new ClientBuilderConfiguration();
            clientBuilderConfiguration.setSignatureVersion(SignVersion.V4);
            OSS ossClient = OSSClientBuilder.create()
                    .endpoint(endpoint)
                    .credentialsProvider(credentialsProvider)
                    .clientConfiguration(clientBuilderConfiguration)
                    .region(region)
                    .build();
    
            // 设置请求头。
            Map<String, String> headers = new HashMap<String, String>();
            // 指定StorageClass。
            headers.put(OSSHeaders.STORAGE_CLASS, StorageClass.Standard.toString());
            // 指定ContentType。
            headers.put(OSSHeaders.CONTENT_TYPE, "text/plain; charset=utf8");
    
            // 设置用户自定义元数据。
            Map<String, String> userMetadata = new HashMap<String, String>();
            userMetadata.put("key1","value1");
            userMetadata.put("key2","value2");
    
            URL signedUrl = null;
            try {
                // 指定生成的预签名URL过期时间,单位为毫秒。本示例以设置过期时间为1小时为例。
                Date expiration = new Date(new Date().getTime() + 3600 * 1000L);
    
                // 生成预签名URL。
                GeneratePresignedUrlRequest request = new GeneratePresignedUrlRequest(bucketName, objectName, HttpMethod.PUT);
                // 设置过期时间。
                request.setExpiration(expiration);
    
                // 将请求头加入到request中。
                request.setHeaders(headers);
                // 添加用户自定义元数据。
                request.setUserMetadata(userMetadata);
    
                // 通过HTTP PUT请求生成预签名URL。
                signedUrl = ossClient.generatePresignedUrl(request);
                // 打印预签名URL。
                System.out.println("signed url for putObject: " + signedUrl);
    
            } catch (OSSException oe) {
                System.out.println("Caught an OSSException, which means your request made it to OSS, "
                        + "but was rejected with an error response for some reason.");
                System.out.println("Error Message:" + oe.getErrorMessage());
                System.out.println("Error Code:" + oe.getErrorCode());
                System.out.println("Request ID:" + oe.getRequestId());
                System.out.println("Host ID:" + oe.getHostId());
            } catch (ClientException ce) {
                System.out.println("Caught an ClientException, which means the client encountered "
                        + "a serious internal problem while trying to communicate with OSS, "
                        + "such as not being able to access the network.");
                System.out.println("Error Message:" + ce.getMessage());
            }
        }
    }       
    package main
    
    import (
    	"context"
    	"flag"
    	"log"
    	"time"
    
    	"github.com/aliyun/alibabacloud-oss-go-sdk-v2/oss"
    	"github.com/aliyun/alibabacloud-oss-go-sdk-v2/oss/credentials"
    )
    
    // 定义全局变量
    var (
    	region     string // 存储区域
    	bucketName string // 存储空间名称
    	objectName string // 对象名称
    )
    
    // init函数用于初始化命令行参数
    func init() {
    	flag.StringVar(&region, "region", "", "The region in which the bucket is located.")
    	flag.StringVar(&bucketName, "bucket", "", "The name of the bucket.")
    	flag.StringVar(&objectName, "object", "", "The name of the object.")
    }
    
    func main() {
    	// 解析命令行参数
    	flag.Parse()
    
    	// 检查bucket名称是否为空
    	if len(bucketName) == 0 {
    		flag.PrintDefaults()
    		log.Fatalf("invalid parameters, bucket name required")
    	}
    
    	// 检查region是否为空
    	if len(region) == 0 {
    		flag.PrintDefaults()
    		log.Fatalf("invalid parameters, region required")
    	}
    
    	// 检查object名称是否为空
    	if len(objectName) == 0 {
    		flag.PrintDefaults()
    		log.Fatalf("invalid parameters, object name required")
    	}
    
    	// 加载默认配置并设置凭证提供者和区域
    	cfg := oss.LoadDefaultConfig().
    		WithCredentialsProvider(credentials.NewEnvironmentVariableCredentialsProvider()).
    		WithRegion(region)
    
    	// 创建OSS客户端
    	client := oss.NewClient(cfg)
    
    	// 生成PutObject的预签名URL
    	result, err := client.Presign(context.TODO(), &oss.PutObjectRequest{
    		Bucket:      oss.Ptr(bucketName),
    		Key:         oss.Ptr(objectName),
    		ContentType:  oss.Ptr("text/plain;charset=utf8"),                    // 请确保在服务端生成该签名URL时设置的ContentType与在使用URL时设置的ContentType一致
    		StorageClass: oss.StorageClassStandard,                              // 请确保在服务端生成该签名URL时设置的StorageClass与在使用URL时设置的StorageClass一致
    		Metadata:    map[string]string{"key1": "value1", "key2": "value2"}, // 请确保在服务端生成该签名URL时设置的Metadata与在使用URL时设置的Metadata一致
    	},
    		oss.PresignExpires(10*time.Minute),
    	)
    	if err != nil {
    		log.Fatalf("failed to put object presign %v", err)
    	}
    
    	log.Printf("request method:%v\n", result.Method)
    	log.Printf("request expiration:%v\n", result.Expiration)
    	log.Printf("request url:%v\n", result.URL)
    	if len(result.SignedHeaders) > 0 {
    		//当返回结果包含签名头时,使用预签名URL发送Put请求时,需要设置相应的请求头
    		log.Printf("signed headers:\n")
    		for k, v := range result.SignedHeaders {
    			log.Printf("%v: %v\n", k, v)
    		}
    	}
    }
    
    import argparse
    import requests
    import alibabacloud_oss_v2 as oss
    
    from datetime import datetime, timedelta
    
    # 创建命令行参数解析器,并描述脚本用途:生成对象预签名PUT请求链接(Presign Put Object)
    parser = argparse.ArgumentParser(description="presign put object sample")
    
    # 定义命令行参数,包括必需的区域、存储空间名称、endpoint以及对象键名
    parser.add_argument('--region', help='The region in which the bucket is located.', required=True)
    parser.add_argument('--bucket', help='The name of the bucket.', required=True)
    parser.add_argument('--endpoint', help='The domain names that other services can use to access OSS')
    parser.add_argument('--key', help='The name of the object.', required=True)
    
    def main():
        # 解析命令行参数,获取用户输入的值
        args = parser.parse_args()
    
        # 从环境变量中加载访问凭证信息,用于身份验证
        credentials_provider = oss.credentials.EnvironmentVariableCredentialsProvider()
    
        # 使用SDK默认配置创建配置对象,并设置认证提供者
        cfg = oss.config.load_default()
        cfg.credentials_provider = credentials_provider
    
        # 设置配置对象的区域属性,根据用户提供的命令行参数
        cfg.region = args.region
    
        # 如果提供了自定义endpoint,则更新配置对象中的endpoint属性
        if args.endpoint is not None:
            cfg.endpoint = args.endpoint
    
        # 使用上述配置初始化OSS客户端,准备与OSS交互
        client = oss.Client(cfg)
    
        # 发送请求以生成指定对象的预签名PUT请求
        pre_result = client.presign(oss.PutObjectRequest(
            bucket=args.bucket,  # 存储空间名
            key=args.key,  # 对象键名
            content_type='text/plain;charset=utf8',  # 指定内容类型
            storage_class='Standard',  # 指定存储类型
            metadata={
                'key1': 'value1',   # 指定元数据
                'key2': 'value2'    # 指定元数据
            }
        ),expires=timedelta(seconds=3600)) # 设置过期时间,单位为秒,此处设置为3600秒
    
    
        # 打印预签名请求的方法、过期时间和URL,以便确认预签名链接的有效性
        print(f'method: {pre_result.method},'
              f' expiration: {pre_result.expiration.strftime("%Y-%m-%dT%H:%M:%S.000Z")},'
              f' url: {pre_result.url}'
              )
    
        # 打印预签名请求的已签名头信息,这些信息在发送实际请求时会被包含在HTTP头部
        for key, value in pre_result.signed_headers.items():
            print(f'signed headers key: {key}, signed headers value: {value}')
    
    # 当此脚本被直接执行时,调用main函数开始处理逻辑
    if __name__ == "__main__":
        main()  # 脚本入口点,控制程序流程从这里开始
  2. 其他人使用预签名URL上传文件并传递相同Header。

    curl
    Java
    Go
    Python
    Node.js
    Browser.js
    C++
    Android
    curl -X PUT \
         -H "Content-Type: text/plain;charset=utf8" \
         -H "x-oss-storage-class: Standard" \
         -H "x-oss-meta-key1: value1" \
         -H "x-oss-meta-key2: value2" \
         -T "C:\\Users\\demo.txt" \
         "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=LTAI****************%2F20241112%2Fcn-hangzhou%2Foss%2Faliyun_v4_request&x-oss-signature=ed5a******************************************************"
    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";
    
            // 设置请求头,这里的请求头信息需要与生成URL时的信息一致。
            Map<String, String> headers = new HashMap<String, String>();
            //指定Object的存储类型。
            headers.put(OSSHeaders.STORAGE_CLASS, StorageClass.Standard.toString());
            //指定ContentType。
            headers.put(OSSHeaders.CONTENT_TYPE, "text/plain;charset=utf8");
    
            // 设置用户自定义元数据,这里的用户自定义元数据需要与生成URL时的信息一致。
            Map<String, String> userMetadata = new HashMap<String, String>();
            userMetadata.put("key1","value1");
            userMetadata.put("key2","value2");
    
            try {
                HttpPut put = new HttpPut(signedUrl.toString());
                System.out.println(put);
                HttpEntity entity = new FileEntity(new File(pathName));
                put.setEntity(entity);
                // 如果生成预签名URL时设置了header参数,例如用户元数据,存储类型等,则调用预签名URL上传文件时,也需要将这些参数发送至服务端。如果签名和发送至服务端的不一致,会报签名错误。
                for(Map.Entry header: headers.entrySet()){
                    put.addHeader(header.getKey().toString(),header.getValue().toString());
                }
                for(Map.Entry meta: userMetadata.entrySet()){
                    // 如果使用userMeta,sdk内部会为userMeta拼接"x-oss-meta-"前缀。当您使用其他方式生成预签名URL进行上传时,userMeta也需要拼接"x-oss-meta-"前缀。
                    put.addHeader("x-oss-meta-"+meta.getKey().toString(), meta.getValue().toString());
                }
    
                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();
            }
        }
    }       
    package main
    
    import (
    	"bytes"
    	"fmt"
    	"io/ioutil"
    	"net/http"
    	"os"
    )
    
    func uploadFile(signedUrl string, filePath string, headers map[string]string, metadata map[string]string) error {
    	// 打开文件
    	file, err := os.Open(filePath)
    	if err != nil {
    		return err
    	}
    	defer file.Close()
    
    	// 读取文件内容
    	fileBytes, err := ioutil.ReadAll(file)
    	if err != nil {
    		return err
    	}
    
    	// 创建请求
    	req, err := http.NewRequest("PUT", signedUrl, bytes.NewBuffer(fileBytes))
    	if err != nil {
    		return err
    	}
    
    	// 设置请求头
    	for key, value := range headers {
    		req.Header.Set(key, value)
    	}
    
    	// 设置用户自定义元数据
    	for key, value := range metadata {
    		req.Header.Set(fmt.Sprintf("x-oss-meta-%s", key), value)
    	}
    
    	// 发送请求
    	client := &http.Client{}
    	resp, err := client.Do(req)
    	if err != nil {
    		return err
    	}
    	defer resp.Body.Close()
    
    	// 处理响应
    	fmt.Printf("返回上传状态码:%d\n", resp.StatusCode)
    	if resp.StatusCode == 200 {
    		fmt.Println("使用网络库上传成功")
    	} else {
    		fmt.Println("上传失败")
    	}
    	body, _ := ioutil.ReadAll(resp.Body)
    	fmt.Println(string(body))
    
    	return nil
    }
    
    func main() {
    	// 将<signedUrl>替换为授权URL。
    	signedUrl := "<signedUrl>"
    
    	// 填写本地文件的完整路径。如果未指定本地路径,则默认从示例程序所属项目对应本地路径中上传文件。
    	filePath := "C:\\Users\\demo.txt"
    
    	// 设置请求头,这里的请求头信息需要与生成URL时的信息一致。
    	headers := map[string]string{
    		"Content-Type": "text/plain;charset=utf8",
    		"x-oss-storage-class": "Standard",
    	}
    
    	// 设置用户自定义元数据,这里的用户自定义元数据需要与生成URL时的信息一致。
    	metadata := map[string]string{
    		"key1": "value1",
    		"key2": "value2",
    	}
    
    	err := uploadFile(signedUrl, filePath, headers, metadata)
    	if err != nil {
    		fmt.Printf("发生错误:%v\n", err)
    	}
    }
    
    import requests
    from requests.auth import HTTPBasicAuth
    import os
    
    def upload_file(signed_url, file_path, headers=None, metadata=None):
        """
        使用预签名的URL上传文件到OSS。
    
        :param signed_url: 预签名的URL。
        :param file_path: 要上传的文件的完整路径。
        :param headers: 可选,自定义HTTP头部。
        :param metadata: 可选,自定义元数据。
        :return: None
        """
        if not headers:
            headers = {}
        if not metadata:
            metadata = {}
    
        # 更新headers,添加元数据前缀
        for key, value in metadata.items():
            headers[f'x-oss-meta-{key}'] = value
    
        try:
            with open(file_path, 'rb') as file:
                response = requests.put(signed_url, data=file, headers=headers)
                print(f"返回上传状态码:{response.status_code}")
                if response.status_code == 200:
                    print("使用网络库上传成功")
                else:
                    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"
    
        # 设置请求头,这里的请求头信息需要与生成URL时的信息一致。
        headers = {
             "Content-Type": "text/plain;charset=utf8",
             "x-oss-storage-class": "Standard"
        }
    
        # 设置用户自定义元数据,这里的用户自定义元数据需要与生成URL时的信息一致。
        metadata = {
             "key1": "value1",
             "key2": "value2"
        }
    
        upload_file(signed_url, file_path, headers, metadata)
    
    const fs = require('fs');
    const axios = require('axios');
    
    async function uploadFile(signedUrl, filePath, headers = {}, metadata = {}) {
        try {
            // 更新headers,添加元数据前缀
            for (const [key, value] of Object.entries(metadata)) {
                headers[`x-oss-meta-${key}`] = value;
            }
    
            // 读取文件流
            const fileStream = fs.createReadStream(filePath);
    
            // 发送PUT请求
            const response = await axios.put(signedUrl, fileStream, {
                headers: headers
            });
    
            console.log(`返回上传状态码:${response.status}`);
            if (response.status === 200) {
                console.log("使用网络库上传成功");
            } else {
                console.log("上传失败");
            }
            console.log(response.data);
        } catch (error) {
            console.error(`发生错误:${error.message}`);
        }
    }
    
    // 主函数
    (async () => {
        // 将<signedUrl>替换为授权URL。
        const signedUrl = "<signedUrl>";
    
        // 填写本地文件的完整路径。如果未指定本地路径,则默认从脚本所在目录中上传文件。
        const filePath = "C:\\Users\\demo.txt";
    
        // 设置请求头,这里的请求头信息需要与生成URL时的信息一致。
        const headers = {
             "Content-Type": "text/plain;charset=utf8",
             "x-oss-storage-class": "Standard"
        };
    
        // 设置用户自定义元数据,这里的用户自定义元数据需要与生成URL时的信息一致。
        const metadata = {
             "key1": "value1",
             "key2": "value2"
        };
    
        await uploadFile(signedUrl, filePath, headers, metadata);
    })();
    
    重要

    如果您使用 Browser.js 上传文件时遇到 403 签名不匹配错误,通常是因为浏览器会自动添加 Content-Type 请求头,而生成预签名 URL 时未指定该请求头,导致签名验证失败。为解决此问题,您需要在生成预签名 URL 时指定 Content-Type 请求头。

    <!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 {
                    alert('Please select a file to upload.');
                }
            });
    
            const upload = async (file, presignedUrl) => {
                const headers = {
                    "Content-Type": "text/plain;charset=utf8",
                    'x-oss-storage-class': 'Standard',
                    'x-oss-meta-key1': 'value1',
                    'x-oss-meta-key2': 'value2'
                };
    
                const response = await fetch(presignedUrl, {
                    method: 'PUT',
                    headers: headers,
                    body: file
                });
    
                if (!response.ok) {
                    throw new Error(`Upload failed, status: ${response.status}`);
                }
    
                alert('File uploaded successfully');
                console.log('File uploaded successfully');
            };
        </script>
    </body>
    </html>
    #include <iostream>
    #include <fstream>
    #include <curl/curl.h>
    #include <map>
    #include <string>
    
    // 回调函数,用于处理HTTP响应
    size_t WriteCallback(void* contents, size_t size, size_t nmemb, std::string* output) {
        size_t totalSize = size * nmemb;
        output->append((char*)contents, totalSize);
        return totalSize;
    }
    
    void uploadFile(const std::string& signedUrl, const std::string& filePath, const std::map<std::string, std::string>& headers, const std::map<std::string, std::string>& metadata) {
        CURL* curl;
        CURLcode res;
        std::string readBuffer;
    
        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);
            rewind(file);
    
            // 设置文件读取回调
            curl_easy_setopt(curl, CURLOPT_READDATA, file);
            curl_easy_setopt(curl, CURLOPT_INFILESIZE_LARGE, (curl_off_t)fileSize);
    
            // 设置请求头
            struct curl_slist* chunk = nullptr;
            for (const auto& header : headers) {
                std::string headerStr = header.first + ": " + header.second;
                chunk = curl_slist_append(chunk, headerStr.c_str());
            }
            for (const auto& meta : metadata) {
                std::string metaStr = "x-oss-meta-" + meta.first + ": " + meta.second;
                chunk = curl_slist_append(chunk, metaStr.c_str());
            }
            curl_easy_setopt(curl, CURLOPT_HTTPHEADER, chunk);
    
            // 设置响应处理回调
            curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteCallback);
            curl_easy_setopt(curl, CURLOPT_WRITEDATA, &readBuffer);
    
            // 执行请求
            res = curl_easy_perform(curl);
    
            // 检查响应
            if (res != CURLE_OK) {
                std::cerr << "curl_easy_perform() failed: " << curl_easy_strerror(res) << std::endl;
            } else {
                long responseCode;
                curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &responseCode);
                std::cout << "返回上传状态码: " << responseCode << std::endl;
                if (responseCode == 200) {
                    std::cout << "使用网络库上传成功" << std::endl;
                } else {
                    std::cout << "上传失败" << std::endl;
                }
                std::cout << readBuffer << std::endl;
            }
    
            // 清理
            fclose(file);
            curl_slist_free_all(chunk);
            curl_easy_cleanup(curl);
        }
    
        curl_global_cleanup();
    }
    
    int main() {
        // 将<signedUrl>替换为授权URL。
        std::string signedUrl = "<signedUrl>";
    
        // 填写本地文件的完整路径。如果未指定本地路径,则默认从示例程序所属项目对应本地路径中上传文件。
        std::string filePath = "C:\\Users\\demo.txt";
    
        // 设置请求头,这里的请求头信息需要与生成URL时的信息一致。
        std::map<std::string, std::string> headers = {
             {"Content-Type", "text/plain;charset=utf8"},
             {"x-oss-storage-class", "Standard"}
        };
    
        // 设置用户自定义元数据,这里的用户自定义元数据需要与生成URL时的信息一致。
        std::map<std::string, std::string> metadata = {
             {"key1", "value1"},
             {"key2", "value2"}
        };
    
        uploadFile(signedUrl, filePath, headers, metadata);
    
        return 0;
    }
    
    import android.os.AsyncTask;
    import android.util.Log;
    
    import java.io.DataOutputStream;
    import java.io.File;
    import java.io.FileInputStream;
    import java.io.IOException;
    import java.io.InputStream;
    import java.net.HttpURLConnection;
    import java.net.URL;
    import java.util.HashMap;
    import java.util.Map;
    import java.util.Map.Entry;
    
    public class SignUrlUploadActivity extends AppCompatActivity {
    
        private static final String TAG = "SignUrlUploadActivity";
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
    
            // 将<signedUrl>替换为授权URL。
            String signedUrl = "<signedUrl>";
    
            // 填写本地文件的完整路径。如果未指定本地路径,则默认从示例程序所属项目对应本地路径中上传文件。
            String pathName = "/storage/emulated/0/demo.txt";
    
            // 设置请求头,这里的请求头信息需要与生成URL时的信息一致。
            Map<String, String> headers = new HashMap<>();
            headers.put("Content-Type", "text/plain;charset=utf8");
            headers.put("x-oss-storage-class", "Standard");
    
            // 设置用户自定义元数据,这里的用户自定义元数据需要与生成URL时的信息一致。
            Map<String, String> userMetadata = new HashMap<>();
            userMetadata.put("key1", "value1");
            userMetadata.put("key2", "value2");
    
            new UploadTask().execute(signedUrl, pathName, headers, userMetadata);
        }
    
        private class UploadTask extends AsyncTask<Object, Void, Integer> {
            @Override
            protected Integer doInBackground(Object... params) {
                String signedUrl = (String) params[0];
                String pathName = (String) params[1];
                Map<String, String> headers = (Map<String, String>) params[2];
                Map<String, String> userMetadata = (Map<String, String>) params[3];
    
                try {
                    URL url = new URL(signedUrl);
                    HttpURLConnection connection = (HttpURLConnection) url.openConnection();
                    connection.setRequestMethod("PUT");
                    connection.setDoOutput(true);
                    connection.setUseCaches(false);
    
                    // 设置请求头
                    for (Entry<String, String> header : headers.entrySet()) {
                        connection.setRequestProperty(header.getKey(), header.getValue());
                    }
    
                    // 设置用户自定义元数据
                    for (Entry<String, String> meta : userMetadata.entrySet()) {
                        connection.setRequestProperty("x-oss-meta-" + meta.getKey(), meta.getValue());
                    }
    
                    // 读取文件
                    File file = new File(pathName);
                    FileInputStream fileInputStream = new FileInputStream(file);
                    DataOutputStream dos = new DataOutputStream(connection.getOutputStream());
    
                    byte[] buffer = new byte[1024];
                    int count;
                    while ((count = fileInputStream.read(buffer)) != -1) {
                        dos.write(buffer, 0, count);
                    }
    
                    fileInputStream.close();
                    dos.flush();
                    dos.close();
    
                    // 获取响应
                    int responseCode = connection.getResponseCode();
                    Log.d(TAG, "返回上传状态码:" + responseCode);
                    if (responseCode == 200) {
                        Log.d(TAG, "使用网络库上传成功");
                    } else {
                        Log.d(TAG, "上传失败");
                    }
    
                    InputStream is = connection.getInputStream();
                    byte[] responseBuffer = new byte[1024];
                    StringBuilder responseStringBuilder = new StringBuilder();
                    while ((count = is.read(responseBuffer)) != -1) {
                        responseStringBuilder.append(new String(responseBuffer, 0, count));
                    }
                    Log.d(TAG, responseStringBuilder.toString());
    
                    return responseCode;
                } catch (IOException e) {
                    e.printStackTrace();
                    return -1;
                }
            }
    
            @Override
            protected void onPostExecute(Integer result) {
                super.onPostExecute(result);
                if (result == 200) {
                    Toast.makeText(SignUrlUploadActivity.this, "上传成功", Toast.LENGTH_SHORT).show();
                } else {
                    Toast.makeText(SignUrlUploadActivity.this, "上传失败", Toast.LENGTH_SHORT).show();
                }
            }
        }
    }
    

设置上传回调

上传时,您可以添加回调参数,让文件上传成功后自动通知应用服务器。回调的详细原理,请参见 Callback

  1. 文件拥有者生成携带上传回调参数的PUT方法的预签名URL。

    Python
    Go
    Java
    PHP
    import argparse
    import base64
    import requests
    import alibabacloud_oss_v2 as oss
    
    from datetime import datetime, timedelta
    
    # 创建命令行参数解析器,并描述脚本用途:生成对象预签名PUT请求链接(Presign Put Object)
    parser = argparse.ArgumentParser(description="presign put object sample")
    
    # 定义命令行参数,包括必需的区域、存储空间名称、endpoint以及对象键名
    parser.add_argument('--region', help='The region in which the bucket is located.', required=True)
    parser.add_argument('--bucket', help='The name of the bucket.', required=True)
    parser.add_argument('--endpoint', help='The domain names that other services can use to access OSS')
    parser.add_argument('--key', help='The name of the object.', required=True)
    
    def main():
        # 解析命令行参数,获取用户输入的值
        args = parser.parse_args()
    
        # 从环境变量中加载访问凭证信息,用于身份验证
        credentials_provider = oss.credentials.EnvironmentVariableCredentialsProvider()
    
        # 使用SDK默认配置创建配置对象,并设置认证提供者
        cfg = oss.config.load_default()
        cfg.credentials_provider = credentials_provider
    
        # 设置配置对象的区域属性,根据用户提供的命令行参数
        cfg.region = args.region
    
        # 如果提供了自定义endpoint,则更新配置对象中的endpoint属性
        if args.endpoint is not None:
            cfg.endpoint = args.endpoint
    
        # 使用上述配置初始化OSS客户端,准备与OSS交互
        client = oss.Client(cfg)
    
        # 请填写您的自定义回调地址
        call_back_url = "http://www.example.com/callback"
        # 构造回调参数(callback):指定回调地址和回调请求体,使用 Base64 编码
        callback=base64.b64encode(str('{\"callbackUrl\":\"' + call_back_url + '\",\"callbackBody\":\"bucket=${bucket}&object=${object}&my_var_1=${x:var1}&my_var_2=${x:var2}\"}').encode()).decode()
        # 构造自定义变量(callback-var),使用 Base64 编码
        callback_var=base64.b64encode('{\"x:var1\":\"value1\",\"x:var2\":\"value2\"}'.encode()).decode()
    
        # 发送请求以生成指定对象的预签名PUT请求
        pre_result = client.presign(oss.PutObjectRequest(
            bucket=args.bucket,  # 存储空间名
            key=args.key,  # 对象键名
            callback=callback,
            callback_var=callback_var,
        ),expires=timedelta(seconds=3600)) # 设置过期时间,单位为秒,此处设置为3600秒
    
        # 打印预签名请求的方法、过期时间和URL,以便确认预签名链接的有效性
        print(f'method: {pre_result.method},'
              f' expiration: {pre_result.expiration.strftime("%Y-%m-%dT%H:%M:%S.000Z")},'
              f' url: {pre_result.url}'
              )
    
        # 打印预签名请求的已签名头信息,这些信息在发送实际请求时会被包含在HTTP头部
        for key, value in pre_result.signed_headers.items():
            print(f'signed headers key: {key}, signed headers value: {value}')
    
    # 当此脚本被直接执行时,调用main函数开始处理逻辑
    if __name__ == "__main__":
        main()  # 脚本入口点,控制程序流程从这里开始
    package main
    
    import (
    	"context"
    	"encoding/base64"
    	"encoding/json"
    	"flag"
    	"log"
    	"time"
    
    	"github.com/aliyun/alibabacloud-oss-go-sdk-v2/oss"
    	"github.com/aliyun/alibabacloud-oss-go-sdk-v2/oss/credentials"
    )
    
    // 定义全局变量
    var (
    	region     string // 存储区域
    	bucketName string // 存储空间名称
    	objectName string // 对象名称
    )
    
    // init函数用于初始化命令行参数
    func init() {
    	flag.StringVar(&region, "region", "", "The region in which the bucket is located.")
    	flag.StringVar(&bucketName, "bucket", "", "The name of the bucket.")
    	flag.StringVar(&objectName, "object", "", "The name of the object.")
    }
    
    func main() {
    	// 解析命令行参数
    	flag.Parse()
    
    	// 检查bucket名称是否为空
    	if len(bucketName) == 0 {
    		flag.PrintDefaults()
    		log.Fatalf("invalid parameters, bucket name required")
    	}
    
    	// 检查region是否为空
    	if len(region) == 0 {
    		flag.PrintDefaults()
    		log.Fatalf("invalid parameters, region required")
    	}
    
    	// 检查object名称是否为空
    	if len(objectName) == 0 {
    		flag.PrintDefaults()
    		log.Fatalf("invalid parameters, object name required")
    	}
    
    	// 加载默认配置并设置凭证提供者和区域
    	cfg := oss.LoadDefaultConfig().
    		WithCredentialsProvider(credentials.NewEnvironmentVariableCredentialsProvider()).
    		WithRegion(region)
    
    	// 创建OSS客户端
    	client := oss.NewClient(cfg)
    
    	// 定义回调参数
    	callbackMap := map[string]string{
    		"callbackUrl":      "http://example.com:23450",                                                                   // 设置回调服务器的URL,例如https://example.com:23450。
    		"callbackBody":     "bucket=${bucket}&object=${object}&size=${size}&my_var_1=${x:my_var1}&my_var_2=${x:my_var2}", // 设置回调请求体。
    		"callbackBodyType": "application/x-www-form-urlencoded",                                                          //设置回调请求体类型。
    	}
    
    	// 将回调参数转换为JSON并进行Base64编码,以便将其作为回调参数传递
    	callbackStr, err := json.Marshal(callbackMap)
    	if err != nil {
    		log.Fatalf("failed to marshal callback map: %v", err)
    	}
    	callbackBase64 := base64.StdEncoding.EncodeToString(callbackStr)
    
    	callbackVarMap := map[string]string{}
    	callbackVarMap["x:my_var1"] = "thi is var 1"
    	callbackVarMap["x:my_var2"] = "thi is var 2"
    	callbackVarStr, err := json.Marshal(callbackVarMap)
    	if err != nil {
    		log.Fatalf("failed to marshal callback var: %v", err)
    	}
    	callbackVarBase64 := base64.StdEncoding.EncodeToString(callbackVarStr)
    
    	// 生成PutObject的预签名URL
    	result, err := client.Presign(context.TODO(), &oss.PutObjectRequest{
    		Bucket:      oss.Ptr(bucketName),
    		Key:         oss.Ptr(objectName),
    		Callback:    oss.Ptr(callbackBase64),    // 设置回调参数,此处为Base64编码后的JSON字符串
    		CallbackVar: oss.Ptr(callbackVarBase64), // 设置自定义回调参数,此处为Base64编码后的JSON字符串
    	},
    		oss.PresignExpires(10*time.Minute),
    	)
    	if err != nil {
    		log.Fatalf("failed to put object presign %v", err)
    	}
    
    	log.Printf("request method:%v\n", result.Method)
    	log.Printf("request expiration:%v\n", result.Expiration)
    	log.Printf("request url:%v\n", result.URL)
    	if len(result.SignedHeaders) > 0 {
    		//当返回结果包含签名头时,使用签名URL发送Put请求时,需要设置相应的请求头
    		log.Printf("signed headers:\n")
    		for k, v := range result.SignedHeaders {
    			log.Printf("%v: %v\n", k, v)
    		}
    	}
    }
    
    import com.aliyun.oss.*;
    import com.aliyun.oss.common.auth.CredentialsProviderFactory;
    import com.aliyun.oss.common.auth.EnvironmentVariableCredentialsProvider;
    import com.aliyun.oss.common.comm.SignVersion;
    import com.aliyun.oss.internal.OSSHeaders;
    import com.aliyun.oss.model.GeneratePresignedUrlRequest;
    
    import java.net.URL;
    import java.text.SimpleDateFormat;
    import java.util.*;
    
    public class OssPresignExample {
        public static void main(String[] args) throws Throwable {
            // 以华东1(杭州)的外网Endpoint为例,其它Region请按实际情况填写。
            String endpoint = "https://oss-cn-hangzhou.aliyuncs.com";
            // 从环境变量中获取访问凭证。运行本代码示例之前,请确保已设置环境变量OSS_ACCESS_KEY_ID和OSS_ACCESS_KEY_SECRET。
            EnvironmentVariableCredentialsProvider credentialsProvider = CredentialsProviderFactory.newEnvironmentVariableCredentialsProvider();
            // 填写Bucket名称,例如examplebucket。
            String bucketName = "examplebucket";
            // 填写Object完整路径,例如exampleobject.txt。Object完整路径中不能包含Bucket名称。
            String objectName = "exampleobject.txt";
            // 填写Bucket所在地域。以华东1(杭州)为例,Region填写为cn-hangzhou。
            String region = "cn-hangzhou";
    
            // 创建OSSClient实例。
            ClientBuilderConfiguration clientBuilderConfiguration = new ClientBuilderConfiguration();
            clientBuilderConfiguration.setSignatureVersion(SignVersion.V4);
            OSS ossClient = OSSClientBuilder.create()
                    .endpoint(endpoint)
                    .credentialsProvider(credentialsProvider)
                    .clientConfiguration(clientBuilderConfiguration)
                    .region(region)
                    .build();
    
            URL signedUrl = null;
            try {
                // 构造回调参数
                String callbackUrl = "http://www.example.com/callback";
                String callbackBody = "{\"callbackUrl\":\"" + callbackUrl + "\",\"callbackBody\":\"bucket=${bucket}&object=${object}&my_var_1=${x:var1}&my_var_2=${x:var2}\"}";
                String callbackBase64 = Base64.getEncoder().encodeToString(callbackBody.getBytes());
    
                String callbackVarJson = "{\"x:var1\":\"value1\",\"x:var2\":\"value2\"}";
                String callbackVarBase64 = Base64.getEncoder().encodeToString(callbackVarJson.getBytes());
                // 设置请求头。
                Map<String, String> headers = new HashMap<String, String>();
                // 指定CALLBACK。
                headers.put(OSSHeaders.OSS_HEADER_CALLBACK, callbackBase64);
                // 指定CALLBACK-VAR。
                headers.put(OSSHeaders.OSS_HEADER_CALLBACK_VAR, callbackVarBase64);
    
                // 设置过期时间(3600秒后)
                Date expiration = new Date(new Date().getTime() + 3600 * 1000);
    
                // 格式化过期时间
                SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'");
                dateFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
                String expirationStr = dateFormat.format(expiration);
    
                // 构造请求
                GeneratePresignedUrlRequest request = new GeneratePresignedUrlRequest(bucketName, objectName);
                request.setMethod(HttpMethod.PUT);
                request.setExpiration(expiration);
                // 将请求头加入到request中。
                request.setHeaders(headers);
    
                //打印callback参数和callback-var参数
                System.out.println("callback:"+callbackBase64);
                System.out.println("callback-var:"+callbackVarBase64);
    
                // 生成预签名URL
                URL url = ossClient.generatePresignedUrl(request);
    
                // 输出结果
                System.out.println("method: PUT,");
                System.out.println(" expiration: " + expirationStr + ",");
                System.out.println(" url: " + url);
    
            } catch (OSSException oe) {
                System.out.println("Caught an OSSException, which means your request made it to OSS, "
                        + "but was rejected with an error response for some reason.");
                System.out.println("Error Message:" + oe.getErrorMessage());
                System.out.println("Error Code:" + oe.getErrorCode());
                System.out.println("Request ID:" + oe.getRequestId());
                System.out.println("Host ID:" + oe.getHostId());
            } catch (ClientException ce) {
                System.out.println("Caught an ClientException, which means the client encountered "
                        + "a serious internal problem while trying to communicate with OSS, "
                        + "such as not being able to access the network.");
                System.out.println("Error Message:" + ce.getMessage());
            }
        }
    }
    <?php
    
    // 引入自动加载文件,确保依赖库能够正确加载
    require_once __DIR__ . '/../vendor/autoload.php';
    
    use AlibabaCloud\Oss\V2 as Oss;
    
    // 定义命令行参数的描述信息
    $optsdesc = [
        "region" => ['help' => 'The region in which the bucket is located.', 'required' => True], // Bucket所在的地域(必填)
        "endpoint" => ['help' => 'The domain names that other services can use to access OSS.', 'required' => False], // 访问域名(可选)
        "bucket" => ['help' => 'The name of the bucket', 'required' => True], // Bucket名称(必填)
        "key" => ['help' => 'The name of the object', 'required' => True], // 对象名称(必填)
    ];
    
    // 将参数描述转换为getopt所需的长选项格式
    // 每个参数后面加上":"表示该参数需要值
    $longopts = \array_map(function ($key) {
        return "$key:";
    }, array_keys($optsdesc));
    
    // 解析命令行参数
    $options = getopt("", $longopts);
    
    // 验证必填参数是否存在
    foreach ($optsdesc as $key => $value) {
        if ($value['required'] === True && empty($options[$key])) {
            $help = $value['help']; // 获取参数的帮助信息
            echo "Error: the following arguments are required: --$key, $help" . PHP_EOL;
            exit(1); // 如果必填参数缺失,则退出程序
        }
    }
    
    // 从解析的参数中提取值
    $region = $options["region"]; // Bucket所在的地域
    $bucket = $options["bucket"]; // Bucket名称
    $key = $options["key"];       // 对象名称
    
    // 加载环境变量中的凭证信息
    // 使用EnvironmentVariableCredentialsProvider从环境变量中读取Access Key ID和Access Key Secret
    $credentialsProvider = new Oss\Credentials\EnvironmentVariableCredentialsProvider();
    
    // 使用SDK的默认配置
    $cfg = Oss\Config::loadDefault();
    $cfg->setCredentialsProvider($credentialsProvider); // 设置凭证提供者
    $cfg->setRegion($region); // 设置Bucket所在的地域
    if (isset($options["endpoint"])) {
        $cfg->setEndpoint($options["endpoint"]); // 如果提供了访问域名,则设置endpoint
    }
    
    // 创建OSS客户端实例
    $client = new Oss\Client($cfg);
    
    // 添加x-oss-callback和x-oss-callback-var头部信息
    // 定义回调地址
    $call_back_url = "http://www.example.com/callback";
    
    // 构造回调参数(callback):指定回调地址和回调请求体,使用 Base64 编码
    // 使用占位符 {var1} 和 {var2} 替代 ${x:var1} 和 ${x:var2}
    $callback_body_template = "bucket={bucket}&object={object}&my_var_1={var1}&my_var_2={var2}";
    $callback_body_replaced = str_replace(
        ['{bucket}', '{object}', '{var1}', '{var2}'],
        [$bucket, $key, 'value1', 'value2'],
        $callback_body_template
    );
    $callback = base64_encode(json_encode([
        "callbackUrl" => $call_back_url,
        "callbackBody" => $callback_body_replaced
    ]));
    
    // 构造自定义变量(callback-var),使用 Base64 编码
    $callback_var = base64_encode(json_encode([
        "x:var1" => "value1",
        "x:var2" => "value2"
    ]));
    
    // 创建PutObjectRequest对象,用于上传对象。
    // 注意:此处添加了contentType、metadata和headers参数,用于签名计算。
    $request = new Oss\Models\PutObjectRequest(
        bucket: $bucket,
        key: $key,
        callback:$callback,
        callbackVar:$callback_var,
    );
    
    // 调用presign方法生成预签名URL
    $result = $client->presign($request);
    
    // 打印预签名结果,输出预签名URL,用户可以直接使用该URL进行上传操作
    print(
        'put object presign result:' . var_export($result, true) . PHP_EOL .
        'put object url:' . $result->url . PHP_EOL
    );
    
  2. 其他人使用PUT方法的预签名URL上传文件。

    curl
    Python
    Go
    Java
    PHP
    curl -X PUT \
         -H "x-oss-callback: eyJjYWxsYmFja1VybCI6Imh0dHA6Ly93d3cuZXhhbXBsZS5jb20vY2FsbGJhY2siLCJjYWxsYmFja0JvZHkiOiJidWNrZXQ9JHtidWNrZXR9Jm9iamVjdD0ke29iamVjdH0mbXlfdmFyXzE9JHt4OnZhcjF9Jm15X3Zhcl8yPSR7eDp2YXIyfSJ9" \
         -H "x-oss-callback-var: eyJ4OnZhcjEiOiJ2YWx1ZTEiLCJ4OnZhcjIiOiJ2YWx1ZTIifQ==" \
         -T "C:\\Users\\demo.txt" \
         "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=LTAI****************%2F20241112%2Fcn-hangzhou%2Foss%2Faliyun_v4_request&x-oss-signature=ed5a******************************************************"
    import requests
    
    def upload_file(signed_url, file_path, headers=None):
        """
        使用预签名的URL上传文件到OSS。
    
        :param signed_url: 预签名的URL。
        :param file_path: 要上传的文件的完整路径。
        :param headers: 可选,自定义HTTP头部。
        :return: None
        """
        if not headers:
            headers = {}
    
        try:
            with open(file_path, 'rb') as file:
                response = requests.put(signed_url, data=file, headers=headers)
                print(f"返回上传状态码:{response.status_code}")
                if response.status_code == 200:
                    print("使用网络库上传成功")
                else:
                    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"
    
        headers = {
            "x-oss-callback": "eyJjYWxsYmFja1VybCI6Imh0dHA6Ly93d3cuZXhhbXBsZS5jb20vY2FsbGJhY2siLCJjYWxsYmFja0JvZHkiOiJidWNrZXQ9JHtidWNrZXR9Jm9iamVjdD0ke29iamVjdH0mbXlfdmFyXzE9JHt4OnZhcjF9Jm15X3Zhcl8yPSR7eDp2YXIyfSJ9",
            "x-oss-callback-var": "eyJ4OnZhcjEiOiJ2YWx1ZTEiLCJ4OnZhcjIiOiJ2YWx1ZTIifQ==",
        }
    
        upload_file(signed_url,  file_path, headers)
    package main
    
    import (
    	"bytes"
    	"fmt"
    	"io"
    
    	"net/http"
    	"os"
    )
    
    func uploadFile(signedUrl string, filePath string, headers map[string]string) error {
    	// 打开文件
    	file, err := os.Open(filePath)
    	if err != nil {
    		return err
    	}
    	defer file.Close()
    
    	// 读取文件内容
    	fileBytes, err := io.ReadAll(file)
    	if err != nil {
    		return err
    	}
    
    	// 创建请求
    	req, err := http.NewRequest("PUT", signedUrl, bytes.NewBuffer(fileBytes))
    	if err != nil {
    		return err
    	}
    
    	// 设置请求头
    	for key, value := range headers {
    		req.Header.Add(key, value)
    	}
    
    	// 发送请求
    	client := &http.Client{}
    	resp, err := client.Do(req)
    	if err != nil {
    		return err
    	}
    	defer resp.Body.Close()
    
    	// 处理响应
    	fmt.Printf("返回上传状态码:%d\n", resp.StatusCode)
    	if resp.StatusCode == 200 {
    		fmt.Println("使用网络库上传成功")
    	} else {
    		fmt.Println("上传失败")
    	}
    	body, _ := io.ReadAll(resp.Body)
    	fmt.Println(string(body))
    
    	return nil
    }
    
    func main() {
    	// 将<signedUrl>替换为授权URL。
    	signedUrl := "<signedUrl>"
    	// 填写本地文件的完整路径。如果未指定本地路径,则默认从示例程序所属项目对应本地路径中上传文件。
    	filePath := "C:\\Users\\demo.txt"
    
    	// 设置请求头,这里的请求头信息需要与生成URL时的信息一致。
    	headers := map[string]string{
    		"x-oss-callback":     "eyJjYWxsYmFja1VybCI6Imh0dHA6Ly93d3cuZXhhbXBsZS5jb20vY2FsbGJhY2siLCJjYWxsYmFja0JvZHkiOiJidWNrZXQ9JHtidWNrZXR9Jm9iamVjdD0ke29iamVjdH0mbXlfdmFyXzE9JHt4OnZhcjF9Jm15X3Zhcl8yPSR7eDp2YXIyfSJ9",
    		"x-oss-callback-var": "eyJ4OnZhcjEiOiJ2YWx1ZTEiLCJ4OnZhcjIiOiJ2YWx1ZTIifQ==",
    	}
    
    	err := uploadFile(signedUrl, filePath, headers)
    	if err != nil {
    		fmt.Printf("发生错误:%v\n", err)
    	}
    }
    
    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";
    
            // 设置请求头,包括x-oss-callback和x-oss-callback-var。
            Map<String, String> headers = new HashMap<String, String>();
            headers.put("x-oss-callback", "eyJjYWxsYmFja1VybCI6Imh0dHA6Ly93d3cuZXhhbXBsZS5jb20vY2FsbGJhY2siLCJjYWxsYmFja0JvZHkiOiJidWNrZXQ9JHtidWNrZXR9Jm9iamVjdD0ke29iamVjdH0mbXlfdmFyXzE9JHt4OnZhcjF9Jm15X3Zhcl8yPSR7eDp2YXIyfSJ9");
            headers.put("x-oss-callback-var", "eyJ4OnZhcjEiOiJ2YWx1ZTEiLCJ4OnZhcjIiOiJ2YWx1ZTIifQ==");
    
            try {
                HttpPut put = new HttpPut(signedUrl.toString());
                System.out.println(put);
                HttpEntity entity = new FileEntity(new File(pathName));
                put.setEntity(entity);
                // 如果生成预签名URL时设置了header参数,则调用预签名URL上传文件时,也需要将这些参数发送至服务端。如果签名和发送至服务端的不一致,会报签名错误。
                for(Map.Entry header: headers.entrySet()){
                    put.addHeader(header.getKey().toString(),header.getValue().toString());
                }
    
                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();
            }
        }
    }       
    <?php
    
    function uploadFile($signedUrl, $filePath, $headers = []) {
        // 检查文件是否存在
        if (!file_exists($filePath)) {
            echo "文件不存在: $filePath\n";
            return;
        }
    
        // 初始化cURL会话
        $ch = curl_init();
    
        // 设置cURL选项
        curl_setopt($ch, CURLOPT_URL, $signedUrl);
        curl_setopt($ch, CURLOPT_PUT, true);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($ch, CURLOPT_INFILE, fopen($filePath, 'rb'));
        curl_setopt($ch, CURLOPT_INFILESIZE, filesize($filePath));
        curl_setopt($ch, CURLOPT_HTTPHEADER, array_map(function($key, $value) {
            return "$key: $value";
        }, array_keys($headers), $headers));
    
        // 执行cURL请求
        $response = curl_exec($ch);
    
        // 获取HTTP状态码
        $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
    
        // 关闭cURL会话
        curl_close($ch);
    
        // 输出结果
        echo "返回上传状态码:$httpCode\n";
        if ($httpCode == 200) {
            echo "使用网络库上传成功\n";
        } else {
            echo "上传失败\n";
        }
        echo $response . "\n";
    }
    
    // 将<signedUrl>替换为授权URL。
    $signedUrl = "<signedUrl>";
    
    // 填写本地文件的完整路径。如果未指定本地路径,则默认从脚本所在目录中上传文件。
    $filePath = "C:\\Users\\demo.txt";
    
    $headers = [
        "x-oss-callback" => "eyJjYWxsYmFja1VybCI6Imh0dHA6Ly93d3cuZXhhbXBsZS5jb20vY2FsbGJhY2siLCJjYWxsYmFja0JvZHkiOiJidWNrZXQ9JHtidWNrZXR9Jm9iamVjdD0ke29iamVjdH0mbXlfdmFyXzE9JHt4OnZhcjF9Jm15X3Zhcl8yPSR7eDp2YXIyfSJ9",
        "x-oss-callback-var" => "eyJ4OnZhcjEiOiJ2YWx1ZTEiLCJ4OnZhcjIiOiJ2YWx1ZTIifQ==",
    ];
    
    uploadFile($signedUrl, $filePath, $headers);
    
    ?>

了解更多

什么是签名URL

预签名URL是一种安全链接,通过加密签名和有效期验证,临时授权访问特定的OSS文件。生成预签名URL时,本地基于AK/SK密钥对资源路径、过期时间等参数进行加密计算,生成签名参数并将其添加到URL中,形成完整的预签名URL。其典型格式为:https://BucketName.Endpoint/Object?签名参数

当第三方访问该URL时,OSS会校验签名参数,若参数被篡改或过期则拒绝访问。

以下为预签名URL示例。

https://examplebucket.oss-cn-hangzhou.aliyuncs.com/exampleobject.txt?x-oss-process=image%2Fresize%2Cp_10&x-oss-date=20241115T095058Z&x-oss-expires=3600&x-oss-signature-version=OSS4-HMAC-SHA256&x-oss-credential=LTAI****************%2F20241115%2Fcn-hangzhou%2Foss%2Faliyun_v4_request&x-oss-signature=6e7a********************************

通过这种方式,文件拥有者可以安全地授权第三方访问文件,而无需暴露密钥。

适用场景

  • 短期文件共享:第三方申请上传或下载指定文件,后端便生成带有效期的预签名URL并返回给前端。第三方可以在有效期内使用该URL上传或下载文件,从而保障数据安全。

  • 灵活访问:文件拥有者可以通过邮件或聊天工具等方式分享预签名URL,第三方获取URL后在浏览器地址栏粘贴即可下载文件。

常见问题

如何授权第三方对OSS资源进行更多操作,而不仅限于上传?

除了预签名URL,阿里云还提供了更灵活的临时授权方式——STS临时访问凭证。如果您希望第三方对OSS的操作不只局限于上传,而是可以执行例如:列举文件、拷贝文件等更多OSS资源的管理操作,建议您了解并使用STS临时访问凭证,详情请参见使用STS临时访问凭证访问OSS

是否可以仅允许指定网站访问OSS资源,拒绝其他来源的请求?

是的,您可以通过配置Referer防盗链,设置白名单,限制只有通过特定网站(如您的网站)才能访问OSS资源,从而防止其他恶意来源未通过您的网站而直接引用您的OSS文件。详细配置步骤,请参见配置Referer防盗链来阻止其他网站引用OSS文件

报错CORS错误

没有配置Bucket跨域策略或跨域策略配置错误,请参考跨域设置进行配置检查。

报错405 Method Not Allowed

请求方法不正确。使用签名URL上传文件时,注意使用PUT请求非POST请求。

  • 本页导读 (1)
  • 注意事项
  • 流程概览
  • 使用预签名URL上传单个文件
  • 设置Header定义上传策略
  • 设置上传回调
  • 了解更多
  • 什么是预签名URL
  • 常见问题
  • 如何授权第三方对OSS资源进行更多操作,而不仅限于上传?
  • 是否可以仅允许指定网站访问OSS资源,拒绝其他来源的请求?
  • 报错CORS错误
  • 报错405 Method Not Allowed
AI助理

点击开启售前

在线咨询服务

你好,我是AI助理

可以解答问题、推荐解决方案等