如何在 Go 中将预签名 POST 上传到 AWS S3?

mur*_*att 3 amazon-s3 go pre-signed-url

我想做一个 预先签名的 POST 以将文件上传到 AWS S3 存储桶- 在 Go 中这将如何完成?

请注意,这与使用 PUT 预签名上传不同。

mur*_*att 5

所以为了帮助别人,我会自己回答这个问题,并提供一些代码来帮助可能有同样问题的其他人。

可以在此处找到 Google App Engine 呈现预签名 POST 表单的示例 Web 应用程序。

还有一个我创建的小型库,用于在 Go 中进行预签名 POST

简而言之,对公共读取的 Amazon S3 存储桶执行预签名 POST,您需要:

1. 将 S3 存储桶配置为仅允许公开下载。

仅允许公开读取的存储桶策略示例。

{
    "Version": "2012-10-17",
    "Id": "akjsdhakshfjlashdf",
    "Statement": [
        {
            "Sid": "kjahsdkajhsdkjasda",
            "Effect": "Allow",
            "Principal": {
                "AWS": "*"
            },
            "Action": "s3:GetObject",
            "Resource": "arn:aws:s3:::BUCKETNAMEHERE/*"
        }
    ]
}
Run Code Online (Sandbox Code Playgroud)

2. 为允许上传的 HTTP POST 创建策略。

AWS S3 文档

示例 POST 策略模板,将特定密钥上传到特定存储桶并允许公共读取访问。

{ "expiration": "%s",
    "conditions": [
        {"bucket": "%s"},
        ["starts-with", "$key", "%s"],
        {"acl": "public-read"},

        {"x-amz-credential": "%s"},
        {"x-amz-algorithm": "AWS4-HMAC-SHA256"},
        {"x-amz-date": "%s" }
    ]
}
Run Code Online (Sandbox Code Playgroud)

3. 使用 S3 存储桶所有者的凭据生成并签署策略。

AWS 文档

  • 填写到期、存储桶、密钥、凭证和日期的正确值。
  • base64 编码策略。
  • HMAC-SHA256 获取签名的策略。
  • 对签名进行十六进制编码。

4.构造和POST多部分表单数据

AWS S3 文档

现在,您可以生成一个 HTML 表单并自动获取正确的多部分表单数据请求,如上述链接中所述。

我想在 Go 中手动做到这一点,所以这里是如何做到这一点。

无论哪种方式,您都需要提供您在步骤 2 和 3 中创建的 POST 策略中指定的所有部分。除了必填字段(不在策略中)之外,您也不能在请求中包含其他字段。

还指定了字段的顺序,它们都是 HTTP POST 请求中的多部分字段。

func Upload(url string, fields Fields) error {
    var b bytes.Buffer
    w := multipart.NewWriter(&b)
    for _, f := range fields {
            fw, err := w.CreateFormField(f.Key)
            if err != nil {
                    return err
            }
            if _, err := fw.Write([]byte(f.Value)); err != nil {
                    return err
            }
    }
    w.Close()

    req, err := http.NewRequest("POST", url, &b)
    if err != nil {
            return err
    }
    req.Header.Set("Content-Type", w.FormDataContentType())

    client := &http.Client{}
    res, err := client.Do(req)
    if err != nil {
            return err
    }
    if res.StatusCode != http.StatusOK {
            err = fmt.Errorf("bad status: %s", res.Status)
    }
    return nil
}
Run Code Online (Sandbox Code Playgroud)