1.《獲取STS臨時授權憑證》
2.《透過STS Token分片上傳檔案》
一、相關文件
1.AWS S3預簽名URL文件:https://docs.aws.amazon.com/zh_cn/AmazonS3/latest/userguide/example_s3_Scenario_PresignedUrl_section.html
AWS S3只針對檔案的儲存,若想實現阿里雲oss透過URL引數對圖片進行處理,則需要使用AWS CloudFront。
CloudFront支援圖片處理的解決方案:https://www.amazonaws.cn/solutions/technology/app-development/serverless-image-handler/?nc1=h_ls
CloudFront支援圖片處理的實施文件:https://aws-gcr-solutions.s3.cn-north-1.amazonaws.com.cn/cn-serverless-image-handler/latest/docs.zh.pdf
CloudFront預簽名URL:https://aws-gcr-solutions.s3.cn-north-1.amazonaws.com.cn/cn-serverless-image-handler/latest/docs.zh.pdf
二、GO示例
需要自己建立公私鑰
1. Create private key:
openssl genrsa -out private_key.pem 2048
2. Create public key from private key.
openssl rsa -pubout -in private_key.pem -out public_key.pem
私鑰用於配置在程式碼中,公鑰用於AWS控制檯配置,會生成一個KeyPairID;
配置了CDN,則走CDN預簽名;否則,走bucket預簽名
package main import ( "crypto" "crypto/rsa" "crypto/sha1" "crypto/x509" "encoding/base64" "encoding/pem" "errors" "fmt" "github.com/aws/aws-sdk-go-v2/aws" "github.com/aws/aws-sdk-go-v2/config" "github.com/aws/aws-sdk-go-v2/credentials" "github.com/aws/aws-sdk-go-v2/service/s3" "strings" "time" "context" ) var PrivatePemKey = `-----BEGIN RSA PRIVATE KEY----- {私鑰檔案內容} -----END RSA PRIVATE KEY-----` var KeyPairID = "{keypid}" //AWS控制檯PairID func main() { cfg := &StoreClientConf{ RoleArn: "{bucket roleArn}", Region: "{bucket region}", AccessKeyID: "{bucket ak}", AccessKeySecret: "{bucket sk}", CDNDomain: "{cdn 域名}", //透過該引數可控制使用S3的預簽名,還是使用CDN預簽名 } client := NewAwsClient(cfg) objectKey := "aws/20240422104414.png" var bucketName = "{bucket name}" url, err := client.SignUrl(context.Background(), bucketName, objectKey, 86400, "image/resize,w_200,h_100") if err != nil { fmt.Println("client.SignUrl err: " + err.Error()) } fmt.Println("sign url: " + url) } type AwsClient struct { roleArn string region string accessKeyID string accessKeySecret string cdnDomain string } type StoreClientConf struct { RoleArn string Region string AccessKeyID string AccessKeySecret string CDNDomain string } func NewAwsClient(cfg *StoreClientConf) *AwsClient { return &AwsClient{ roleArn: cfg.RoleArn, region: cfg.Region, accessKeyID: cfg.AccessKeyID, accessKeySecret: cfg.AccessKeySecret, cdnDomain: cfg.CDNDomain, //開啟了AWS CloudFront CDN,可支援URL引數處理圖片 } } func (s *AwsClient) SignUrl(ctx context.Context, bucketName, objectKey string, expire int32, ossProcess string) (string, error) { // 判斷是走S3的預簽名還是走CDN的預簽名 if s.cdnDomain == "" { //S3的檔案預簽名URL,不支援圖片處理 return s.S3SignUrl(ctx, bucketName, objectKey, expire) } return s.CDNSignUrl(ctx, objectKey, expire, ossProcess) //AWS CDN預簽名,支援URL引數處理圖片 } func (s *AwsClient) CDNSignUrl(ctx context.Context, objectKey string, expire int32, ossProcess string) (string, error) { objectKey = strings.TrimLeft(objectKey, "/") //去除最左邊的/ path := "https://" + s.cdnDomain + "/" + objectKey if ossProcess != "" { path += "?x-oss-process=" + ossProcess } expires := time.Now().Add(time.Duration(expire) * time.Second).Unix() separator := "?" if strings.Contains(path, "?") { separator = "&" } policy := fmt.Sprintf(`{"Statement":[{"Resource":"%s","Condition":{"DateLessThan":{"AWS:EpochTime":%d}}}]}`, path, expires) signature, err := rsaSHA1Sign(policy) if err != nil { return "", errors.New("CDNSignUrl rsaSHA1Sign err: " + err.Error()) } signatureEncoded := urlSafeBase64Encode(signature) signUrl := fmt.Sprintf("%s%sExpires=%d&Signature=%s&Key-Pair-Id=%s", path, separator, expires, signatureEncoded, KeyPairID) return signUrl, nil } // https://docs.aws.amazon.com/zh_cn/AmazonS3/latest/userguide/example_s3_Scenario_PresignedUrl_section.html func (s *AwsClient) S3SignUrl(ctx context.Context, bucketName, objectKey string, expire int32) (string, error) { // 1.初始化客戶端 cfg, err := s.loadConfig(ctx) if err != nil { return "", err } client := s3.NewFromConfig(cfg) pClient := s3.NewPresignClient(client) // 2.呼叫s3介面,獲取檔案預簽名URL input := &s3.GetObjectInput{ Bucket: aws.String(bucketName), Key: aws.String(objectKey), } response, err := pClient.PresignGetObject(ctx, input, func(opts *s3.PresignOptions) { opts.Expires = time.Duration(expire) * time.Second }) if err != nil { return "", errors.New("SignUrl pClient.PresignGetObject err: "+err.Error()) } if response == nil { return "", errors.New("SignUrl response is nil") } return response.URL, nil } func (s *AwsClient) loadConfig(ctx context.Context) (aws.Config, error) { cfg, err := config.LoadDefaultConfig(ctx, config.WithRegion(s.region), config.WithCredentialsProvider(credentials.StaticCredentialsProvider{ Value: aws.Credentials{ AccessKeyID: s.accessKeyID, SecretAccessKey: s.accessKeySecret, SessionToken: "", Source: "", }, }), ) if err != nil { fmt.Println("awsClient LoadDefaultConfig err:" + err.Error()) return aws.Config{}, errors.New("awsClient LoadDefaultConfig err") } return cfg, nil } func rsaSHA1Sign(policy string) ([]byte, error) { // Load the private key privateKeyBlock, _ := pem.Decode([]byte(PrivatePemKey)) privateKey, err := x509.ParsePKCS1PrivateKey(privateKeyBlock.Bytes) if err != nil { return nil, err } // Compute the signature hashed := sha1.Sum([]byte(policy)) signature, err := rsa.SignPKCS1v15(nil, privateKey, crypto.SHA1, hashed[:]) // if err != nil { return nil, err } return signature, nil } func urlSafeBase64Encode(value []byte) string { encoded := base64.StdEncoding.EncodeToString(value) // Replace unsafe characters +, = and / with the safe characters -, _, and ~ encoded = strings.ReplaceAll(encoded, "+", "-") encoded = strings.ReplaceAll(encoded, "=", "_") encoded = strings.ReplaceAll(encoded, "/", "~") return encoded }