默认情况下,OSS Bucket中的文件是私有的,仅文件拥有者拥有上传权限。您可以使用OSS Java SDK生成签名URL,以允许他人通过该URL上传文件。在生成签名URL时,可以自定义其过期时间以限制访问持续时长。在签名URL有效期内,该URL可被多次访问。如果多次执行上传操作,会有文件覆盖的风险。超出有效期后,将无法进行上传,此时需要重新生成签名URL。
注意事项
本文以华东1(杭州)外网Endpoint为例。如果您希望通过与OSS同地域的其他阿里云产品访问OSS,请使用内网Endpoint。关于OSS支持的Region与Endpoint的对应关系,请参见OSS地域和访问域名。
本文以从环境变量读取访问凭证为例。如何配置访问凭证,请参见Java配置访问凭证。
本文以OSS域名新建OSSClient为例。如果您希望通过自定义域名、STS等方式新建OSSClient,请参见新建OSSClient。
生成用于上传的签名URL时,您必须具有
oss:PutObject
权限。具体操作,请参见为RAM用户授权自定义的权限策略。说明生成签名URL过程中,SDK利用本地存储的密钥信息,根据特定算法计算出签名(signature),然后将其附加到URL上,以确保URL的有效性和安全性。这一系列计算和构造URL的操作都是在客户端完成,不涉及网络请求到服务端。因此,生成签名URL时不需要授予调用者特定权限。但是,为避免第三方用户无法对签名URL授权的资源执行相关操作,需要确保调用生成签名URL接口的身份主体被授予对应的权限。
本文以V4签名URL为例,有效期最大为7天。更多信息,请参见签名版本4(推荐)。
使用过程
使用PUT方式的签名URL上传文件的过程如下:
代码示例
文件拥有者生成PUT方法的签名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()); } } }
其他人使用PUT方法的签名URL上传文件。
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(); } } }
curl -X PUT -T /path/to/local/file "https://exampleobject.oss-cn-hangzhou.aliyuncs.com/exampleobject.txt?x-oss-date=20241112T083238Z&x-oss-expires=3599&x-oss-signature-version=OSS4-HMAC-SHA256&x-oss-credential=LTAI5************%2F20241112%2Fcn-hangzhou%2Foss%2Faliyun_v4_request&x-oss-signature=ed5a939feb8d79a389572719f7e2939939936d0**********"
import 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); })();
#include <iostream> #include <fstream> #include <curl/curl.h> void uploadFile(const std::string& signedUrl, const std::string& filePath) { CURL *curl; CURLcode res; curl_global_init(CURL_GLOBAL_DEFAULT); curl = curl_easy_init(); if (curl) { // 设置URL curl_easy_setopt(curl, CURLOPT_URL, signedUrl.c_str()); // 设置请求方法为PUT curl_easy_setopt(curl, CURLOPT_UPLOAD, 1L); // 打开文件 FILE *file = fopen(filePath.c_str(), "rb"); if (!file) { std::cerr << "无法打开文件: " << filePath << std::endl; return; } // 获取文件大小 fseek(file, 0, SEEK_END); long fileSize = ftell(file); fseek(file, 0, SEEK_SET); // 设置文件大小 curl_easy_setopt(curl, CURLOPT_INFILESIZE_LARGE, (curl_off_t)fileSize); // 设置输入文件句柄 curl_easy_setopt(curl, CURLOPT_READDATA, file); // 执行请求 res = curl_easy_perform(curl); if (res != CURLE_OK) { std::cerr << "curl_easy_perform() 失败: " << curl_easy_strerror(res) << std::endl; } else { long httpCode = 0; curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &httpCode); std::cout << "返回上传状态码: " << httpCode << std::endl; if (httpCode == 200) { std::cout << "使用网络库上传成功" << std::endl; } } // 关闭文件 fclose(file); // 清理 curl_easy_cleanup(curl); } curl_global_cleanup(); } int main() { // 将<signedUrl>替换为授权URL。 std::string signedUrl = "<signedUrl>"; // 填写本地文件的完整路径。如果未指定本地路径,则默认从示例程序所属项目对应本地路径中上传文件。 std::string filePath = "C:\\Users\\demo.txt"; uploadFile(signedUrl, filePath); return 0; }
package main import ( "fmt" "io" "net/http" "os" ) func uploadFile(signedUrl, filePath string) error { // 打开文件 file, err := os.Open(filePath) if err != nil { return fmt.Errorf("无法打开文件: %w", err) } defer file.Close() // 创建一个新的HTTP客户端 client := &http.Client{} // 创建一个PUT请求 req, err := http.NewRequest("PUT", signedUrl, file) if err != nil { return fmt.Errorf("创建请求失败: %w", err) } // 发送请求 resp, err := client.Do(req) if err != nil { return fmt.Errorf("发送请求失败: %w", err) } defer resp.Body.Close() // 读取响应 body, err := io.ReadAll(resp.Body) if err != nil { return fmt.Errorf("读取响应失败: %w", err) } fmt.Printf("返回上传状态码: %d\n", resp.StatusCode) if resp.StatusCode == 200 { fmt.Println("使用网络库上传成功") } fmt.Println(string(body)) return nil } func main() { // 将<signedUrl>替换为授权URL。 signedUrl := "<signedUrl>" // 填写本地文件的完整路径。如果未指定本地路径,则默认从示例程序所属项目对应本地路径中上传文件。 filePath := "C:\\Users\\demo.txt" err := uploadFile(signedUrl, filePath) if err != nil { fmt.Println("发生错误:", err) } }
package com.example.signurlupload; import android.os.AsyncTask; import android.util.Log; import java.io.DataOutputStream; import java.io.FileInputStream; import java.io.IOException; import java.net.HttpURLConnection; import java.net.URL; public class SignUrlUploadActivity { private static final String TAG = "SignUrlUploadActivity"; public void uploadFile(String signedUrl, String filePath) { new UploadTask().execute(signedUrl, filePath); } private class UploadTask extends AsyncTask<String, Void, String> { @Override protected String doInBackground(String... params) { String signedUrl = params[0]; String filePath = params[1]; HttpURLConnection connection = null; DataOutputStream dos = null; FileInputStream fis = null; try { URL url = new URL(signedUrl); connection = (HttpURLConnection) url.openConnection(); connection.setRequestMethod("PUT"); connection.setDoOutput(true); connection.setRequestProperty("Content-Type", "application/octet-stream"); fis = new FileInputStream(filePath); dos = new DataOutputStream(connection.getOutputStream()); byte[] buffer = new byte[1024]; int length; while ((length = fis.read(buffer)) != -1) { dos.write(buffer, 0, length); } dos.flush(); dos.close(); fis.close(); int responseCode = connection.getResponseCode(); Log.d(TAG, "返回上传状态码: " + responseCode); if (responseCode == 200) { Log.d(TAG, "使用网络库上传成功"); } return "上传完成,状态码: " + responseCode; } catch (IOException e) { e.printStackTrace(); return "上传失败: " + e.getMessage(); } finally { if (connection != null) { connection.disconnect(); } } } @Override protected void onPostExecute(String result) { Log.d(TAG, result); } } public static void main(String[] args) { SignUrlUploadActivity activity = new SignUrlUploadActivity(); // 将<signedUrl>替换为授权URL。 String signedUrl = "<signedUrl>"; // 填写本地文件的完整路径。如果未指定本地路径,则默认从示例程序所属项目对应本地路径中上传文件。 String filePath = "C:\\Users\\demo.txt"; activity.uploadFile(signedUrl, filePath); } }
其他场景
使用签名URL指定请求头和自定义元数据上传文件
使用签名URL分片上传文件
常见问题
使用临时签名进行文件上传时,在上传过程中签名过期了,上传中的文件会失败吗?
如果我在生成URL时未设置请求头和自定义元数据,在使用URL上传时还需要配置吗?
相关文档
关于获取V4签名URL的完整示例代码,请参见GitHub示例。
关于使用签名URL上传文件的API接口说明,请参见GeneratePresignedUrlRequest。