POST s3 预签名 url 调用邮递员 returns SignatureDoesNotMatch

POST s3 presigned url call in postman returns SignatureDoesNotMatch

当调用从我的 node.js 服务器返回的预签名 POST 端点时,我收到看起来像有效响应的内容:

请注意,我正在设置环境变量以使 POST 调用更容易重复。

当调用预签名 URL 时,我收到了带有 SignatureDoesNotMatch 错误代码的 403 响应。我很难找到有关使用 POST 预签名 url 的资源。所以我的问题如下:

  1. 我是否正确调用了预签名 URL(见下文)?
  2. 我如何进行调试以了解此处发生的情况(我已尝试打开存储桶日志,但未记录任何内容)?
  3. 我还可以进一步了解什么来解决这个问题?

执行 post 时,我的理解是调用 POST 返回 url 并使用 form-data 包括 body 最后的密钥被标记为文件,并附有要上传的文件。因此,根据以上回复,我在 postman 中使用了以下调用: Headers:

Body(我已经尝试过包含和不包含 Content-Type):

然而,当我进行此调用时,我收到了 403 禁止响应和 SignatureDoesNotMatch 错误代码:

这里是生成预签名 URL 的代码(使用 "@aws-sdk/client-s3": "^3.25.0", "@aws-sdk/s3-presigned-post": package.json 内的 "^3.25.0"):

const { createPresignedPost } = require("@aws-sdk/s3-presigned-post");
const { S3Client } = require("@aws-sdk/client-s3");

const s3 = new S3Client({
  credentials: {
    accessKeyId: process.env.S3_ACCESS_KEY_ID,
    secretAccessKey: process.env.S3_SECRET_ACCESS_KEY,
  },
  signatureVersion: "v4",
  region: "eu-west-2",
});
async function getSignedUrl() {

  const params = {
    Bucket: "richbits-test",
    Key: "d3c0c9a0-ff91-11eb-bbe6-b9d90cd8bb8f",
    Conditions: [["eq", "$Content-Type", "image/jpeg"]],
  };

  console.log(params);

  const signedUrl = await createPresignedPost(s3, params);

  return signedUrl;
}

我已经仔细检查了存储桶和密钥,但希望能就我如何前进或任何有用的资源提供一些建议,以帮助我更好地理解这些预签名 url 的使用。

哇哦,AWS 关于这些 headers 的文档太糟糕了。这是我想出来的。

  1. createPresignedPost() 的输出是一个 object,有两个键:urlfieldsfields object 包含提交 post 时必须使用的所有表单字段和相应的值。您可以将这些字段并且仅将这些字段复制到您的 Postman 请求中。
  2. AWS SDK会自动添加几个字段,包括bucketkeypolicy以及生成签名所需的几个字段。对于 JavaScript SDK,它们是 X-Amz-AlgorithmX-Amz-CredentialX-Amz-DateX-Amz-Signature(尽管对于 Python SDK,它们是 AWSAccessKeyIdsignature)。这些将在 createPresignedPost()fields 密钥中返回,因此您无需手动生成它们。
  3. createPresignedPost()Fields 参数用于您要添加到表单的其他字段。 here. However, the SDK handles many of these for you, particularly the required ones. According to the Python SDK docs 中列出了可能添加的整组字段,Fields 中可以包含的字段有 aclCache-ControlContent-TypeContent-DispositionContent-EncodingExpiressuccess_action_redirectredirectsuccess_action_statusx-amz-meta-.
  4. createPresignedPost()FieldsConditions 参数需要同步,这样 Fields 中的任何字段在 Conditions 中也有条件,反之亦然相反。

综上所述,我认为您的 getSignedUrl() 函数的问题在于它缺少 createPresignedPost()Fields 参数,而 key-value 对 Content-Type(因为你有 Content-Type 的条件)。如果 Fields 参数中没有 Content-Type,计算出的签名将不会包含 Content-Type 字段。如果您随后遗漏了 Content-Type 字段,您的签名可能匹配,但您的政策条件将失败,因为它要求 Content-Type 存在并等于 image/jpeg。如果您包含 Content-Type,那么您的签名将不匹配。

这是适合您的方法。

const { createPresignedPost } = require("@aws-sdk/s3-presigned-post");
const { S3Client } = require("@aws-sdk/client-s3");

const s3 = new S3Client({
  credentials: {
    accessKeyId: process.env.AWS_ACCESS_KEY_ID,
    secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY,
  },
  signatureVersion: "v4",
  region: "eu-west-2",
});
async function getSignedUrl() {

  const params = {
    Bucket: "richbits-test",
    Key: "d3c0c9a0-ff91-11eb-bbe6-b9d90cd8bb8f",
    Conditions: [["eq", "$Content-Type", "image/jpeg"]],
    Fields: {"Content-Type": "image/jpeg"},
  };

  console.log(params);

  const signedUrl = await createPresignedPost(s3, params);

  return signedUrl;
}
getSignedUrl().then(res => {
        console.log(res)
})

这个输出应该是

{
  Bucket: 'richbits-test',
  Key: 'd3c0c9a0-ff91-11eb-bbe6-b9d90cd8bb8f',
  Conditions: [ [ 'eq', '$Content-Type', 'image/jpeg' ] ],
  Fields: { 'Content-Type': 'image/jpeg' }
}
{
  url: 'https://s3.eu-west-2.amazonaws.com/richbits-test',
  fields: {
    'Content-Type': 'image/jpeg',
    bucket: 'richbits-test',
    'X-Amz-Algorithm': 'AWS4-HMAC-SHA256',
    'X-Amz-Credential': '<YOUR_ACCESS_KEY_ID>/20211005/eu-west-2/s3/aws4_request',
    'X-Amz-Date': '20211005T111446Z',
    key: 'd3c0c9a0-ff91-11eb-bbe6-b9d90cd8bb8f',
    Policy: '<SOME_BASE64_ENCODED_STRING>',
    'X-Amz-Signature': '<THE_SIGNATURE>'
  }
}

那么您的 POST 请求将包含表单字段 Content-TypebucketX-Amz-AlgorithmX-Amz-CredentialX-Amz-DatePolicy,以及 X-Amz-Signature。这是 HTML 表单的样子。

<html>
  <head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
  </head>
  <body>

  <form action="https://s3.eu-west-2.amazonaws.com/richbits-test" method="post" enctype="multipart/form-data">
    Bucket:
    <input type="input" name="bucket" value="richbits-test" /><br />
    Key to upload: 
    <input type="input"  name="key" value="d3c0c9a0-ff91-11eb-bbe6-b9d90cd8bb8f" /><br />
    Content-Type: 
    <input type="input"  name="Content-Type" value="image/jpeg" /><br />
    <input type="text"   name="X-Amz-Credential" value="<YOUR_ACCESS_KEY_ID>/20211005/eu-west-2/s3/aws4_request" />
    <input type="text"   name="X-Amz-Date" value="20211005T111446Z" />

    <input type="hidden" name="Policy" value="<SOME_BASE64_ENCODED_STRING>" />'
    <input type="hidden" name="X-Amz-Algorithm" value="AWS4-HMAC-SHA256" />
    <input type="hidden" name="X-Amz-Signature" value="<THE_SIGNATURE>" />
    
    File: 
    <input type="file"   name="file" /> <br />
    <!-- The elements after this will be ignored -->
    <input type="submit" name="submit" value="Upload to Amazon S3" />
  </form>
  
</html>

就调试这个问题而言,我没有发现 POST 对 S3 的请求有任何用处。我没有得到任何回应或未授权状态,没有任何迹象表明问题出在哪里。