默认情况下,OSS Bucket中的文件是私有的,仅文件拥有者可访问。本文介绍如何使用OSS Go SDK生成带有过期时间的PUT方法签名URL,以允许他人临时上传文件。在有效期内可多次访问,超期后需重新生成。
注意事项
本文示例代码以华东1(杭州)的地域ID
cn-hangzhou
为例,默认使用外网Endpoint,如果您希望通过与OSS同地域的其他阿里云产品访问OSS,请使用内网Endpoint。关于OSS支持的Region与Endpoint的对应关系,请参见OSS地域和访问域名。本文以从环境变量读取访问凭证为例。如何配置访问凭证,请参见配置访问凭证。
生成PUT方法的签名URL时,您必须具有
oss:PutObject
权限。具体操作,请参见为RAM用户授权自定义的权限策略。说明在生成签名 URL 的过程中,SDK 利用本地存储的密钥信息,根据特定算法计算出签名(signature),然后将其附加到 URL 上,以确保 URL 的有效性和安全性。这一系列计算和构造 URL 的操作都是在客户端完成的,不涉及网络请求到服务端。因此,生成签名 URL 时不需要授予调用者特定权限。然而,为避免第三方用户无法对签名URL授权的资源执行相关操作,需要确保调用生成签名 URL 接口的身份主体已被授予相应的权限。
本文示例代码使用V4签名URL,有效期最大为7天。更多信息,请参见签名版本4(推荐)。
方法定义
您可以使用预签名接口生成预签名URL,授予对存储空间中对象的限时访问权限。在过期时间之前,您可以多次使用预签名URL。
预签名接口定义如下:
func (c *Client) Presign(ctx context.Context, request any, optFns ...func(*PresignOptions)) (result *PresignResult, err error)
请求参数列表
参数名 | 类型 | 说明 |
ctx | context.Context | 请求的上下文 |
request | *PutObjectRequest | 设置需要生成签名URL的接口名 |
optFns | ...func(*PresignOptions) | (可选)设置过期时间,如果不指定,默认有效期为15分钟 |
其中,PresignOptions选项列举如下:
选项值 | 类型 | 说明 |
Expires | time.Duration | 从当前时间开始,多长时间过期。例如设置一个有效期为30分钟,30 * time.Minute |
Expiration | time.Time | 绝对过期时间 |
在签名版本V4下,有效期最长为7天。同时设置Expiration和Expires时,优先取Expiration。
返回值列表
返回值名 | 类型 | 说明 |
result | *PresignResult | 返回结果,包含预签名URL,HTTP方法,过期时间和参与签名的请求头 |
err | error | 请求的状态,当请求失败时,err不为nil |
其中,PresignResult返回值列举如下:
参数名 | 类型 | 说明 |
Method | string | HTTP方法,和接口对应,例如PutObject接口,返回PUT |
URL | string | 预签名URL |
Expiration | time.Time | 签名URL的过期时间 |
SignedHeaders | map[string]string | 被签名的请求头,例如PutObject接口,设置了Content-Type时,会返回 Content-Type的信息 |
示例代码
文件拥有者生成PUT方法的签名URL。
重要在生成PUT方法的签名URL时,如果指定了请求头,确保在通过该签名URL发起PUT请求时也包含相应的请求头,以免出现不一致,导致请求失败和签名错误。
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/txt"), // 请确保在服务端生成该签名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) } } }
其他人使用PUT方法的签名URL上传文件。
package main import ( "bytes" "fmt" "io" "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 := 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.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, _ := 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{ // "Content-Type": "text/txt", } // 设置用户自定义元数据,这里的用户自定义元数据需要与生成URL时的信息一致。 metadata := map[string]string{ // "key1": "value1", // "key2": "value2", } err := uploadFile(signedUrl, filePath, headers, metadata) if err != nil { fmt.Printf("发生错误:%v\n", err) } }