NS-Vue/Rails 对 S3 存储桶的预签名 PUT 请求给出 403

Posted

技术标签:

【中文标题】NS-Vue/Rails 对 S3 存储桶的预签名 PUT 请求给出 403【英文标题】:NS-Vue/Rails Presigned PUT request to S3 bucket giving 403 【发布时间】:2020-11-15 22:59:10 【问题描述】:

我的前端是一个 Nativescript-Vue 应用程序。后端是 Rails。我从 rrails 服务器获取了一个预签名的 url,并使用它在客户端发送一个 put 请求以进行图像上传。在this 之后,我在rails 上生成了一个预签名的url:

def create_presigned_url
  filename = "#self.id.jpg"

  Aws.config[:credentials]=Aws::Credentials.new(
    "secret_id",
    "secret_key")
  s3 = Aws::S3::Resource.new(region: 'ap-southeast-1')

  bucket = 'bucket_name'
  obj = s3.bucket(bucket).object(filename)
  self.presigned_url = obj.presigned_url(:put,  acl: 'public-read' )
  self.update_column(:image_url, obj.public_url)
end

长话短说,上面的代码生成了一个预签名的 url,我用它在客户端使用 NativeScript-background-http 插件发出请求:

var session = bghttp.session("image-upload");

UploadFile(session, file, url) 
    var request = 
      url: url,
      method: "PUT",
      headers: 
          "Content-Type": "application/octet-stream"
      ,
      description: `Uploading $file.substr(file.lastIndexOf("/") + 1)` 
    ;

    var task = session.uploadFile(file, request);

图片上传正常,显示:

LOG from device Nexus 6P: 'currentBytes: 4096'
LOG from device Nexus 6P: 'totalBytes: 622121'
LOG from device Nexus 6P: 'eventName: progress'
LOG from device Nexus 6P: 'currentBytes: 323584'
LOG from device Nexus 6P: 'totalBytes: 622121'
LOG from device Nexus 6P: 'eventName: progress'
LOG from device Nexus 6P: 'currentBytes: 606208'
LOG from device Nexus 6P: 'eventName: progress'
LOG from device Nexus 6P: 'totalBytes: 622121'
LOG from device Nexus 6P: 'currentBytes: 622121'
LOG from device Nexus 6P: 'totalBytes: 622121'
LOG from device Nexus 6P: 'eventName: progress'
LOG from device Nexus 6P: 'eventName: error'
LOG from device Nexus 6P: 'eventName: 403'
LOG from device Nexus 6P: 'eventName: '

出现403错误,响应为:

<?xml version="1.0" encoding="UTF-8"?>
<Error>
    <Code>SignatureDoesNotMatch</Code>
    <Message>The request signature we calculated does not match the signature you provided. Check your key and signing method.</Message>
...

我用谷歌搜索了这个错误,发现 SO 上的所有响应都是关于使用不正确的 AWS 密钥,但是,我确保我在 Rails 上拥有正确的 AWS 凭证。我怀疑它可能与生成预签名网址时的内容类型有关,但我不确定。我的存储桶权限似乎是正确的,但我可能在那里遗漏了一些东西。我已经设置了政策和 CORS。

这是存储桶策略:


    "Version": "2012-10-17",
    "Id": "my-policy-id",
    "Statement": [
        
            "Sid": "my-sid",
            "Effect": "Allow",
            "Principal": 
                "AWS": "arn:aws:iam::my-id:user/my-user"
            ,
            "Action": "s3:PutObject",
            "Resource": "arn:aws:s3:::my-bucket/*"
        
    ]

这是 CORS:

<?xml version="1.0" encoding="UTF-8"?>
<CORSConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
<CORSRule>
    <AllowedOrigin>*</AllowedOrigin>
    <AllowedMethod>POST</AllowedMethod>
    <AllowedMethod>PUT</AllowedMethod>
    <AllowedMethod>GET</AllowedMethod>
    <AllowedMethod>DELETE</AllowedMethod>
    <AllowedMethod>HEAD</AllowedMethod>
    <ExposeHeader>ETag</ExposeHeader>
    <AllowedHeader>*</AllowedHeader>
</CORSRule>
</CORSConfiguration>

我的 IAM 用户也有必要的政策。

任何见解将不胜感激。

编辑:我什至删除了存储桶策略并授予对存储桶的所有公共访问权限,但我仍然看到 403 错误。错误是signature 之一。

【问题讨论】:

【参考方案1】:

我不得不将self.presigned_url = obj.presigned_url(:put, acl: 'public-read' ) 更改为self.presigned_url = obj.presigned_url(:put, expires_in: 10*60, content_type: 'application/octet-stream')

以及所有人到公开列表、私有写入的存储桶 ACL。桶策略到


    "Version": "2012-10-17",
    "Id": "policy_id",
    "Statement": [
        
            "Sid": "my_statement_id",
            "Effect": "Allow",
            "Principal": 
                "AWS": "arn:aws:iam::user_id:user/iam_user"
            ,
            "Action": "s3:PutObject",
            "Resource": "arn:aws:s3:::my_bucket/*"
        ,
        
            "Sid": "PublicRead",
            "Effect": "Allow",
            "Principal": "*",
            "Action": [
                "s3:GetObject",
                "s3:GetObjectVersion"
            ],
            "Resource": "arn:aws:s3:::my_bucket/*"
        
    ]

【讨论】:

以上是关于NS-Vue/Rails 对 S3 存储桶的预签名 PUT 请求给出 403的主要内容,如果未能解决你的问题,请参考以下文章

如何允许 cognito 身份验证的用户获得对 s3 存储桶的公共访问权限

S3:如何授予对多个存储桶的访问权限?

通过使用 AWS-SDK PHP 生成的预签名帖子拒绝 AWS S3 上传访问

通过使用 AWS-SDK PHP 生成的预签名帖子拒绝 AWS S3 上传访问

如何使用 Picasso 使用动态 URL 缓存来自 S3 存储桶的图像?

使用 Cloudfront 限制对 S3 存储桶的访问