默认情况下,OSS存储空间中文件的读写权限是私有,仅文件拥有者可以上传。但文件拥有者可以对指定的文件生成具有临时访问权限的预签名URL,以允许他人使用该预签名URL在有效期内上传文件。该功能适用于授权合作伙伴上传合同,用户上传头像等场景。
注意事项
本文以华东1(杭州)外网Endpoint为例。如果您希望通过与OSS同地域的其他阿里云产品访问OSS,请使用内网Endpoint。关于OSS支持的Region与Endpoint的对应关系,请参见OSS地域和访问域名。
预签名URL无需权限即可生成,但仅当您拥有
oss:PutObject
权限时,第三方才能通过该预签名URL成功上传文件。具体授权操作,请参见为RAM用户授权自定义的权限策略。预签名URL上传不支持上传FormData格式,若需使用FormData上传数据,建议使用OSS表单上传。
流程概览
使用预签名URL上传文件的过程如下:
使用预签名URL上传单个文件
Bucket拥有者生成PUT方法的预签名URL
通过SDK生成的预签名URL,最大有效时长为7天。若使用STSToken来生成预签名URL,则最大有效时长为43200秒(12小时)。
JavaGoPythonNode.jsPHPAndroidC++iOS.NETC更多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(®ion, "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; }
其他人使用PUT方法生成的预签名URL上传文件。
预签名URL在有效期内可多次访问,但多次执行上传操作,会有文件覆盖的风险。超期后,需执行步骤一重新生成预签名URL以继续访问文件。
curlJavaGopythonNode.jsbrowser.jsC++Androidcurl -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来管理文件,详情请参见管理文件元数据。
文件拥有者生成带Header参数的预签名URL。
JavaGoPythonimport 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(®ion, "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() # 脚本入口点,控制程序流程从这里开始
其他人使用预签名URL上传文件并传递相同Header。
curlJavaGoPythonNode.jsBrowser.jsC++Androidcurl -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。
文件拥有者生成携带上传回调参数的PUT方法的预签名URL。
PythonGoJavaPHPimport 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(®ion, "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 );
其他人使用PUT方法的预签名URL上传文件。
curlPythonGoJavaPHPcurl -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后在浏览器地址栏粘贴即可下载文件。
常见问题
- 本页导读 (1)
- 注意事项
- 流程概览
- 使用预签名URL上传单个文件
- 设置Header定义上传策略
- 设置上传回调
- 了解更多
- 什么是预签名URL
- 常见问题
- 如何授权第三方对OSS资源进行更多操作,而不仅限于上传?
- 是否可以仅允许指定网站访问OSS资源,拒绝其他来源的请求?
- 报错CORS错误
- 报错405 Method Not Allowed