全部产品
Search
文档中心

对象存储 OSS:使用预签名URL上传文件

更新时间:Feb 25, 2025

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

注意事项

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

  • 生成PUT方法的预签名URL时,您必须具有oss:PutObject权限。具体操作,请参见RAM Policy常见示例

    说明

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

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

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

流程概览

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

image

详细步骤

步骤一:Bucket拥有者生成PUT方法的签名URL

说明

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

Java

更多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());
        }
    }
}       

Go

更多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)
		}
	}
}

Python

更多SDK信息,请参见Python使用预签名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'

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

Node.js

此处仅列举普通场景,如何生成带图片处理参数的预签名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);
});

PHP

此处仅列举普通场景,如何生成带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;
}           

Android

更多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();
}

C++

更多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;
}

iOS

更多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;
}];

.NET

更多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.";
// 创建OSSClient实例。
var client = new OssClient(endpoint, accessKeyId, accessKeySecret);
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);
}

C

更多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;
}

步骤二:其他人使用PUT方法生成的签名URL上传文件。

使用curl命令上传

curl -X PUT -T /path/to/local/file "https://exampleobject.oss-cn-hangzhou.aliyuncs.com/exampleobject.txt?x-oss-date=20241112T083238Z&x-oss-expires=3599&x-oss-signature-version=OSS4-HMAC-SHA256&x-oss-credential=LTAI5************%2F20241112%2Fcn-hangzhou%2Foss%2Faliyun_v4_request&x-oss-signature=ed5a939feb8d79a389572719f7e2939939936d0**********"

使用代码上传

Java

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();
        }
    }
}       

Python

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)

Go

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)
	}
}

Node.js

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

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>File Upload Example</title>
</head>
<body>
    <h1>File Upload Example</h1>

    <input type="file" id="fileInput" />
    <button id="uploadButton">Upload File</button>

    <script>
        // 请将此替换为实际的预签名 URL
        const signedUrl = "<signedUrl>"; 

        document.getElementById('uploadButton').addEventListener('click', async () => {
            const fileInput = document.getElementById('fileInput');
            const file = fileInput.files[0];

            if (file) {
                try {
                    await upload(file, signedUrl);
                } catch (error) {
                    console.error('Error during upload:', error);
                    alert('Upload failed: ' + error.message);
                }
            } else {
                console.error('Please select a file');
                alert('Please select a file to upload.');
            }
        });

        const upload = async (file, presignedUrl) => {
            const chunkSize = 1024 * 1024;
            let start = 0;
            let end = chunkSize;

            while (start < file.size) {
                const chunk = file.slice(start, end);
                const response = await fetch(presignedUrl, {
                    method: 'PUT',
                    body: chunk
                });

                if (!response.ok) {
                    throw new Error(`Upload failed for chunk, status: ${response.status}`);
                }

                console.log('Chunk uploaded successfully');
                start = end;
                end = start + chunkSize;
            }

            console.log('File uploaded successfully');
        };
    </script>
</body>
</html>

C++

#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;
}

Android

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);
    }
}

iOS

// 通过预签名URL上传文件。
NSURL * url = [NSURL URLWithString:urlString];
NSMutableURLRequest * request = [NSMutableURLRequest requestWithURL:url];
request.HTTPMethod = @"PUT";
request.allHTTPHeaderFields = @{OSSHttpHeaderContentType: contentType};
NSURLSession * session = [NSURLSession sharedSession];
NSURLSessionTask * sessionTask = [session uploadTaskWithRequest:request
                                                       fromFile:file
                                              completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
    if (error) {
        NSLog(@"upload error: %@", error);
        return;
    } else if (((NSHTTPURLResponse*)response).statusCode == 203 ||
               ((NSHTTPURLResponse*)response).statusCode >= 300) {
        NSString *body = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
        NSLog(@"upload error: %@", body);
        return;
    }
    NSLog(@"upload success");
}];
[sessionTask resume];

其他场景

使用预签名URL分片上传文件

使用预签名URL分片上传文件需要配置分片大小并逐片生成预签名URL,示例代码如下:

Java

import com.aliyun.oss.*;
import com.aliyun.oss.common.auth.*;
import com.aliyun.oss.common.comm.SignVersion;
import com.aliyun.oss.common.utils.CRC64;
import com.aliyun.oss.model.*;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpPut;
import org.apache.http.entity.BufferedHttpEntity;
import org.apache.http.entity.InputStreamEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.DefaultHttpRequestRetryHandler;
import org.apache.http.impl.client.HttpClients;
import java.io.*;
import java.net.URL;
import java.util.*;
import java.util.Date;

public class MultipartUrl {
    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";
        // 填写本地文件的完整路径。如果未指定本地路径,则默认从示例程序所属项目对应本地路径中上传文件。
        String pathName = "D:\\localpath\\examplefile.txt";
        // 指定生成的预签名URL过期时间,单位为毫秒。本示例以设置过期时间为1小时为例。
        long expireTime = 3600*1000L;
        // 填写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();

        // 创建InitiateMultipartUploadRequest对象。
        InitiateMultipartUploadRequest initRequest = new InitiateMultipartUploadRequest(bucketName, objectName);

        // 初始化分片。
        InitiateMultipartUploadResult upResult = ossClient.initiateMultipartUpload(initRequest);
        // 返回uploadId。uploadId是分片上传事件的唯一标识。您可以根据该uploadId发起相关的操作,例如取消分片上传、查询分片上传等。
        String uploadId = upResult.getUploadId();

        // partETags是PartETag的集合。PartETag由分片的ETag和分片号组成。
        List<PartETag> partETags =  new ArrayList<PartETag>();
        // 每个分片的大小,用于计算文件有多少个分片。单位为字节。
        long partSize = 1 * 100 * 1024L;   //100kb。

        // 填写本地文件的完整路径。如果未指定本地路径,则默认从示例程序所属项目对应本地路径中上传文件。
        File sampleFile = new File(pathName);
        long fileLength = sampleFile.length();
        // 如果希望设置为1个分片,可以将分片大小设置为文件大小。
        // long fileLength = sampleFile.length();
        int partCount = (int) (fileLength / partSize);
        if (fileLength % partSize != 0) {
            partCount++;
        }

        // 设置预签名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");*/


        // 遍历分片获取分片签名,并上传分片。
        // 您还可以一次返回所有分片的预签名URL,然后依次上传。此处以返回单个预签名URL,并通过预签名URL上传单个分片为例。
        for (int i = 0; i < partCount; i++) {
            long startPos = i * partSize;
            long curPartSize = (i + 1 == partCount) ? (fileLength - startPos) : partSize;

            /*// 设置md5校验,只支持对单个分片进行md5校验
            FileInputStream inStream = new FileInputStream(pathName);
            // 跳过已经上传的分片。
            inStream.skip(startPos);
            BoundedInputStream entity = new BoundedInputStream(inStream, partSize);
            String md5 = BinaryUtil.toBase64String(DigestUtils.md5(entity));
            headers.put("Content-MD5", md5);*/

            String signUrl = getSignUrl(ossClient, bucketName, objectName, HttpMethod.PUT, expireTime, i + 1, uploadId, headers);

            // 通过预签名URL上传文件,以HttpClients为例说明。
            putObjectWithHttp(signUrl, pathName, startPos, curPartSize, headers);
        }

        // 假设合并分片时,与上传分片不在同一个系统。此时,您需要先列举分片,然后再合并分片。
        // 列举已上传的分片。
        ListPartsRequest listPartsRequest = new ListPartsRequest(bucketName, objectName, uploadId);
        PartListing partListing = ossClient.listParts(listPartsRequest);

        // 遍历分片,并填充partETags。
        for (PartSummary part : partListing.getParts()) {
            PartETag partETag = new PartETag(part.getPartNumber(), part.getETag());
            partETags.add(partETag);
        }

        // 合并分片。
        CompleteMultipartUploadRequest completeMultipartUploadRequest =
                new CompleteMultipartUploadRequest(bucketName, objectName, uploadId, partETags);
        // String md5 = BinaryUtil.toBase64String(BinaryUtil.calculateMd5("aaa".getBytes()));
        // 设置禁止覆盖同名文件。
        // completeMultipartUploadRequest.addHeader("x-oss-forbid-overwrite", "true");

        // 完成分片上传。
        CompleteMultipartUploadResult completeMultipartUploadResult = ossClient.completeMultipartUpload(completeMultipartUploadRequest);
        System.out.println("合并分片成功,上传分片完成。");


        // 校验整体上传文件是否完整
        CRC64 crc = new CRC64();
        InputStream inStream = new FileInputStream(pathName);
        byte[] bytes = new byte[1024];
        int cnt;
        while ((cnt = inStream.read(bytes)) != -1) {
            crc.update(bytes, 0, cnt);
        }

        if(crc.getValue() == completeMultipartUploadResult.getServerCRC()){
            System.out.println("上传文件完整");
        } else {
            System.out.println("上传文件不完整,请做异常处理");
        }
    }

    public static void putObjectWithHttp(String signedUrl, String pathName, long startPos, long partSize, Map<String, String> headers) throws IOException {
        CloseableHttpClient httpClient = null;
        CloseableHttpResponse response = null;
        try {
            HttpPut put = new HttpPut(signedUrl);

            FileInputStream inStream = new FileInputStream(pathName);
            // 跳过已经上传的分片。
            inStream.skip(startPos);
            InputStreamEntity entity = new InputStreamEntity(inStream, partSize);
            BufferedHttpEntity byteArrayEntity = new BufferedHttpEntity(entity);
            put.setEntity(byteArrayEntity);

            // 如果生成预签名URL时设置了header参数,例如用户元数据,存储类型等,则调用预签名URL上传文件时,也需要将这些参数发送至服务端。如果签名和发送至服务端的不一致,会报签名错误。
            for(Map.Entry header: headers.entrySet()){
                put.addHeader(header.getKey().toString(),header.getValue().toString());
            }

            // 加入重试,设置为重试3次。这里仅为举例,业务代码根据需要自行设置重试
            httpClient = HttpClients.custom().setRetryHandler(new DefaultHttpRequestRetryHandler(3, true)).build();

            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 {
            if(response != null){
                response.close();
            }
            if(httpClient != null){
                httpClient.close();
            }
        }
    }

    public static String getSignUrl(OSS ossClient, String bucketName, String objectName, HttpMethod method, long expireTime, int partNum, String uploadId, Map<String, String> headers){
        // 指定生成的预签名URL过期时间,单位为毫秒。
        Date expiration = new Date(new Date().getTime() + expireTime);

        // 生成预签名URL。
        GeneratePresignedUrlRequest request = new GeneratePresignedUrlRequest(bucketName, objectName, method);
        // 设置过期时间。
        request.setExpiration(expiration);

        // 将请求头加入到request中。
        request.setHeaders(headers);

        request.addQueryParameter("partNumber", String.valueOf(partNum));

        request.addQueryParameter("uploadId", uploadId);


        // 通过HTTP Method请求生成预签名URL。
        URL signedUrl = ossClient.generatePresignedUrl(request);
        // 打印预签名URL。
        System.out.println("signed url: " + signedUrl);
        return signedUrl.toString();
    }
}

Go

package main

import (
	"bytes"
	"context"
	"flag"
	"fmt"
	"log"
	"net/http"
	"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                     // 对象名称
	length     = int64(5000 * 1024)       // 文件总长度,单位为字节
	partSize   = int64(200 * 1024)        // 每个分片的大小,单位为字节
	partsNum   = int(length/partSize + 1) // 分片的数量
	data       = make([]byte, length)     // 模拟的数据,用于上传
)

// 初始化命令行参数
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()

	// 检查必要参数是否已设置
	if len(bucketName) == 0 {
		flag.PrintDefaults()
		log.Fatalf("invalid parameters, bucket name required")
	}

	if len(region) == 0 {
		flag.PrintDefaults()
		log.Fatalf("invalid parameters, region required")
	}

	if len(objectName) == 0 {
		flag.PrintDefaults()
		log.Fatalf("invalid parameters, object name required")
	}

	// 配置 OSS 客户端
	cfg := oss.LoadDefaultConfig().
		WithCredentialsProvider(credentials.NewEnvironmentVariableCredentialsProvider()).
		WithRegion(region)

	// 创建 OSS 客户端
	client := oss.NewClient(cfg)

	// 初始化分片上传
	initResult, err := client.InitiateMultipartUpload(context.TODO(), &oss.InitiateMultipartUploadRequest{
		Bucket: oss.Ptr(bucketName),
		Key:    oss.Ptr(objectName),
	})
	if err != nil {
		log.Fatalf("failed InitiateMultipartUpload %v", err)
	}

	// 遍历每个分片,生成签名 URL 并上传分片
	for i := 0; i < partsNum; i++ {
		start := int64(i) * partSize
		end := start + partSize
		if end > length {
			end = length
		}
		signedResult, err := client.Presign(context.TODO(), &oss.UploadPartRequest{
			Bucket:     oss.Ptr(bucketName),
			Key:        oss.Ptr(objectName),
			PartNumber: int32(i + 1),
			Body:       bytes.NewReader(data[start:end]),
			UploadId:   initResult.UploadId,
		}, oss.PresignExpiration(time.Now().Add(1*time.Hour))) // 生成签名 URL,有效期为1小时
		if err != nil {
			log.Fatalf("failed to generate presigned URL %v", err)
		}
		fmt.Printf("signed url:%#v\n", signedResult.URL) // 打印生成的签名URL

		// 创建HTTP请求并上传分片
		req, err := http.NewRequest(signedResult.Method, signedResult.URL, bytes.NewReader(data[start:end]))
		if err != nil {
			log.Fatalf("failed to create HTTP request %v", err)
		}

		c := &http.Client{} // 创建HTTP客户端
		_, err = c.Do(req)
		if err != nil {
			log.Fatalf("failed to upload part by signed URL %v", err)
		}
	}

	// 列举已上传的分片
	partsResult, err := client.ListParts(context.TODO(), &oss.ListPartsRequest{
		Bucket:   oss.Ptr(bucketName),
		Key:      oss.Ptr(objectName),
		UploadId: initResult.UploadId,
	})
	if err != nil {
		log.Fatalf("failed to list parts %v", err)
	}

	// 收集已上传的分片信息
	var parts []oss.UploadPart
	for _, p := range partsResult.Parts {
		parts = append(parts, oss.UploadPart{PartNumber: p.PartNumber, ETag: p.ETag})
	}

	// 完成分片上传
	result, err := client.CompleteMultipartUpload(context.TODO(), &oss.CompleteMultipartUploadRequest{
		Bucket:   oss.Ptr(bucketName),
		Key:      oss.Ptr(objectName),
		UploadId: initResult.UploadId,
		CompleteMultipartUpload: &oss.CompleteMultipartUpload{
			Parts: parts,
		},
	})
	if err != nil {
		log.Fatalf("failed to complete multipart upload %v", err)
	}

	// 打印完成分片上传的结果
	log.Printf("complete multipart upload result:%#v\n", result)
}

使用预签名URL定义上传策略(携带Header)

通过携带Header参数,您可以定义上传策略(如指定文件存储类型或设置文件ACL)。如果在生成预签名URL时指定了请求头,也要确保在使用预签名URL时包含相应的请求头,以免出现不一致,导致请求失败和签名错误。可设置的Header参数,请参见PutObject。您还可以自定义元数据以便文件管理,如何自定义元数据请参见管理文件元数据

例如,如果生成预签名URL时设置了Content-Typex-oss-storage-class这两个Header参数,则在使用该URL上传时也需要设置Content-Typex-oss-storage-class这两个Header,否则会返回403。

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

Java

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 = "zy-test1009";
        // 填写Object完整路径,例如exampleobject.txt。Object完整路径中不能包含Bucket名称。
        String objectName = "exampleobject1.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>();
        /*// 指定Object的存储类型。
        headers.put(OSSHeaders.STORAGE_CLASS, StorageClass.Standard.toString());
        // 指定ContentType。
        headers.put(OSSHeaders.CONTENT_TYPE, "text/plain");*/

        // 设置用户自定义元数据。
        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());
        }
    }
}       

Go

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=utf-8"),                    // 请确保在服务端生成该签名URL时设置的ContentType与在使用URL时设置的ContentType一致
		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)
		}
	}
}

Python

# -*- 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; charset=utf8'
# 指定存储类型。
headers["x-oss-storage-class"] = "Standard"

# 指定元数据
metadata = {'key1': 'value1', 'key2': 'value2'}
# 更新headers,添加元数据前缀
for key, value in metadata.items():
    headers[f'x-oss-meta-{key}'] = value

# 生成上传文件的签名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)
  1. 其他人使用PUT方法的预签名URL上传文件。

curl -X PUT \
     -H "Content-Type: text/plain; charset=utf8" \
     -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=LTAI5************%2F20241112%2Fcn-hangzhou%2Foss%2Faliyun_v4_request&x-oss-signature=ed5a939feb8d79a389572719f7e2939939936d0**********"
import org.apache.http.HttpEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpPut;
import org.apache.http.entity.FileEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import java.io.*;
import java.net.URL;
import java.util.*;

public class SignUrlUpload {
    public static void main(String[] args) throws Throwable {
        CloseableHttpClient httpClient = null;
        CloseableHttpResponse response = null;

        // 将<signedUrl>替换为授权URL。
        URL signedUrl = new URL("<signedUrl>");

        // 填写本地文件的完整路径。如果未指定本地路径,则默认从示例程序所属项目对应本地路径中上传文件。
        String pathName = "C:\\Users\\demo.txt";

        // 设置请求头,这里的请求头信息需要与生成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/txt");*/

        // 设置用户自定义元数据,这里的用户自定义元数据需要与生成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();
        }
    }
}       
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/txt",
        // "x-oss-storage-class": "Standard"
    };

    // 设置用户自定义元数据,这里的用户自定义元数据需要与生成URL时的信息一致。
    const metadata = {
        // "key1": "value1",
        // "key2": "value2"
    };

    await uploadFile(signedUrl, filePath, headers, metadata);
})();
<!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>"; 

        // 设置Content-Type、storageClass和metadata
        const contentType = 'text/plain';

        const storageClass = 'Standard';

        const metadata = {
            'x-oss-meta-key1': 'value1',
            'x-oss-meta-key2': 'value2'
        };

        document.getElementById('uploadButton').addEventListener('click', async () => {
            const fileInput = document.getElementById('fileInput');
            const file = fileInput.files[0];

            if (file) {
                try {
                    await upload(file, signedUrl);
                } catch (error) {
                    console.error('Error during upload:', error);
                    alert('Upload failed: ' + error.message);
                }
            } else {
                console.error('Please select a file');
                alert('Please select a file to upload.');
            }
        });

        const upload = async (file, presignedUrl) => {
            const chunkSize = 1024 * 1024;
            let start = 0;
            let end = chunkSize;

            while (start < file.size) {
                const chunk = file.slice(start, end);
                
                const headers = {
                    'Content-Type': contentType,
                    'x-oss-storage-class':storageClass,
                    ...metadata
                };
                
                const response = await fetch(presignedUrl, {
                    method: 'PUT',
                    headers: headers,
                    body: chunk
                });

                if (!response.ok) {
                    throw new Error(`Upload failed for chunk, status: ${response.status}`);
                }

                console.log('Chunk uploaded successfully'); 
                start = end;
                end = start + chunkSize;
            }

            console.log('File uploaded successfully'); 
        };
    </script>
</body>
</html>
#include <iostream>
#include <fstream>
#include <curl/curl.h>
#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/txt"},
        // {"x-oss-storage-class", "Standard"}
    };

    // 设置用户自定义元数据,这里的用户自定义元数据需要与生成URL时的信息一致。
    std::map<std::string, std::string> metadata = {
        // {"key1", "value1"},
        // {"key2", "value2"}
    };

    uploadFile(signedUrl, filePath, headers, metadata);

    return 0;
}
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/txt",
		// "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 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/txt");
        // 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();
            }
        }
    }
}

了解更多

什么是签名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***********1Uuu%2F20241115%2Fcn-hangzhou%2Foss%2Faliyun_v4_request&x-oss-signature=6e7*************************1eb79d96

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

适用场景

  • 短期文件共享:第三方申请上传或下载指定文件,后端便生成带有效期的预签名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请求。