AWS 文件预签名URL

 

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
}

 

posted @ 2024-04-29 16:00  划水的猫  阅读(340)  评论(0编辑  收藏  举报